From c3323be1684444085a301a92b7f3927118b6b87b Mon Sep 17 00:00:00 2001 From: Chad Nehemiah Date: Thu, 29 May 2025 04:07:45 -0500 Subject: [PATCH 01/15] fix: ensure client errors are correctly tracked (#635) * fix: ensure client errors are correctly tracked * chore: update error tracking * chore: adjust clippy * chore: grammatical error --- .../src/handlers/chat_completions.rs | 56 ++++++++++++-- atoma-service/src/handlers/completions.rs | 55 ++++++++++++-- atoma-service/src/handlers/embeddings.rs | 58 +++++++++++--- .../src/handlers/image_generations.rs | 70 +++++++++++++---- atoma-service/src/handlers/metrics.rs | 75 +++++++++++++++++++ 5 files changed, 279 insertions(+), 35 deletions(-) diff --git a/atoma-service/src/handlers/chat_completions.rs b/atoma-service/src/handlers/chat_completions.rs index eb320143..7a2815d0 100644 --- a/atoma-service/src/handlers/chat_completions.rs +++ b/atoma-service/src/handlers/chat_completions.rs @@ -3,7 +3,9 @@ use crate::{ handle_concurrent_requests_count_decrement, metrics::{ CHAT_COMPLETIONS_CONFIDENTIAL_NUM_REQUESTS, CHAT_COMPLETIONS_ESTIMATED_TOTAL_TOKENS, - TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS, TOTAL_FAILED_CHAT_REQUESTS, + TOTAL_BAD_REQUESTS, TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS, + TOTAL_FAILED_CHAT_REQUESTS, TOTAL_LOCKED_REQUESTS, TOTAL_TOO_EARLY_REQUESTS, + TOTAL_TOO_MANY_REQUESTS, TOTAL_UNAUTHORIZED_REQUESTS, }, sign_response_and_update_stack_hash, update_fiat_amount, update_stack_num_compute_units, }, @@ -266,8 +268,30 @@ pub async fn chat_completions_handler( Ok(response) } Err(e) => { - TOTAL_FAILED_CHAT_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + _ => { + TOTAL_FAILED_CHAT_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + } + // NOTE: We need to update the stack number of tokens as the service failed to generate // a proper response. For this reason, we set the total number of tokens to 0. // This will ensure that the stack number of tokens is not updated, and the stack @@ -476,9 +500,29 @@ pub async fn confidential_chat_completions_handler( Ok(response) } Err(e) => { - TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS - .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + _ => { + TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + } // NOTE: We need to update the stack number of tokens as the service failed to generate // a proper response. For this reason, we set the total number of tokens to 0. // This will ensure that the stack number of tokens is not updated, and the stack diff --git a/atoma-service/src/handlers/completions.rs b/atoma-service/src/handlers/completions.rs index fac65537..268d4e4e 100644 --- a/atoma-service/src/handlers/completions.rs +++ b/atoma-service/src/handlers/completions.rs @@ -3,7 +3,9 @@ use crate::{ handle_concurrent_requests_count_decrement, metrics::{ CHAT_COMPLETIONS_CONFIDENTIAL_NUM_REQUESTS, CHAT_COMPLETIONS_ESTIMATED_TOTAL_TOKENS, - TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS, TOTAL_FAILED_CHAT_REQUESTS, + TOTAL_BAD_REQUESTS, TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS, + TOTAL_FAILED_CHAT_REQUESTS, TOTAL_LOCKED_REQUESTS, TOTAL_TOO_EARLY_REQUESTS, + TOTAL_TOO_MANY_REQUESTS, TOTAL_UNAUTHORIZED_REQUESTS, }, sign_response_and_update_stack_hash, update_fiat_amount, update_stack_num_compute_units, }, @@ -240,8 +242,29 @@ pub async fn completions_handler( Ok(response) } Err(e) => { - TOTAL_FAILED_CHAT_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + _ => { + TOTAL_FAILED_CHAT_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + } // NOTE: We need to update the stack number of tokens as the service failed to generate // a proper response. For this reason, we set the total number of tokens to 0. // This will ensure that the stack number of tokens is not updated, and the stack @@ -450,9 +473,29 @@ pub async fn confidential_completions_handler( Ok(response) } Err(e) => { - TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS - .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + _ => { + TOTAL_FAILED_CHAT_CONFIDENTIAL_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.to_owned())]); + } + } if let Some(stack_small_id) = stack_small_id { // NOTE: We need to update the stack number of tokens as the service failed to generate // a proper response. For this reason, we set the total number of tokens to 0. diff --git a/atoma-service/src/handlers/embeddings.rs b/atoma-service/src/handlers/embeddings.rs index 31ce2a8f..7f3ba54d 100644 --- a/atoma-service/src/handlers/embeddings.rs +++ b/atoma-service/src/handlers/embeddings.rs @@ -7,9 +7,10 @@ use crate::{ handle_confidential_compute_encryption_response, metrics::{ TEXT_EMBEDDINGS_CONFIDENTIAL_NUM_REQUESTS, TEXT_EMBEDDINGS_LATENCY_METRICS, - TEXT_EMBEDDINGS_NUM_REQUESTS, TOTAL_COMPLETED_REQUESTS, TOTAL_FAILED_REQUESTS, - TOTAL_FAILED_TEXT_EMBEDDING_CONFIDENTIAL_REQUESTS, - TOTAL_FAILED_TEXT_EMBEDDING_REQUESTS, + TEXT_EMBEDDINGS_NUM_REQUESTS, TOTAL_BAD_REQUESTS, TOTAL_COMPLETED_REQUESTS, + TOTAL_FAILED_REQUESTS, TOTAL_FAILED_TEXT_EMBEDDING_CONFIDENTIAL_REQUESTS, + TOTAL_FAILED_TEXT_EMBEDDING_REQUESTS, TOTAL_LOCKED_REQUESTS, TOTAL_TOO_EARLY_REQUESTS, + TOTAL_TOO_MANY_REQUESTS, TOTAL_UNAUTHORIZED_REQUESTS, }, sign_response_and_update_stack_hash, update_fiat_amount, update_stack_num_compute_units, }, @@ -18,6 +19,7 @@ use crate::{ types::{ConfidentialComputeRequest, ConfidentialComputeResponse}, }; use axum::{extract::State, Extension, Json}; +use hyper::StatusCode; use opentelemetry::KeyValue; use reqwest::Client; use serde_json::Value; @@ -138,9 +140,28 @@ pub async fn embeddings_handler( Ok(response) } Err(e) => { - TOTAL_FAILED_TEXT_EMBEDDING_REQUESTS - .add(1, &[KeyValue::new("model", model.as_str().to_owned())]); - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new("model", model.as_str().to_owned())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + _ => { + TOTAL_FAILED_TEXT_EMBEDDING_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + } if let Some(stack_small_id) = stack_small_id { let concurrent_requests = handle_concurrent_requests_count_decrement( &state.concurrent_requests_per_stack, @@ -312,9 +333,28 @@ pub async fn confidential_embeddings_handler( Ok(response) } Err(e) => { - TOTAL_FAILED_TEXT_EMBEDDING_CONFIDENTIAL_REQUESTS - .add(1, &[KeyValue::new("model", model.as_str().to_owned())]); - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new("model", model.as_str().to_owned())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + _ => { + TOTAL_FAILED_TEXT_EMBEDDING_CONFIDENTIAL_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + } if let Some(stack_small_id) = stack_small_id { let concurrent_requests = handle_concurrent_requests_count_decrement( &state.concurrent_requests_per_stack, diff --git a/atoma-service/src/handlers/image_generations.rs b/atoma-service/src/handlers/image_generations.rs index 12b2251a..b1fb79c4 100644 --- a/atoma-service/src/handlers/image_generations.rs +++ b/atoma-service/src/handlers/image_generations.rs @@ -6,8 +6,10 @@ use crate::{ handle_concurrent_requests_count_decrement, metrics::{ IMAGE_GEN_CONFIDENTIAL_NUM_REQUESTS, IMAGE_GEN_LATENCY_METRICS, IMAGE_GEN_NUM_REQUESTS, - TOTAL_COMPLETED_REQUESTS, TOTAL_FAILED_IMAGE_CONFIDENTIAL_GENERATION_REQUESTS, - TOTAL_FAILED_IMAGE_GENERATION_REQUESTS, TOTAL_FAILED_REQUESTS, + TOTAL_BAD_REQUESTS, TOTAL_COMPLETED_REQUESTS, + TOTAL_FAILED_IMAGE_CONFIDENTIAL_GENERATION_REQUESTS, + TOTAL_FAILED_IMAGE_GENERATION_REQUESTS, TOTAL_FAILED_REQUESTS, TOTAL_LOCKED_REQUESTS, + TOTAL_TOO_EARLY_REQUESTS, TOTAL_TOO_MANY_REQUESTS, TOTAL_UNAUTHORIZED_REQUESTS, }, update_fiat_amount, update_stack_num_compute_units, }, @@ -16,6 +18,7 @@ use crate::{ types::{ConfidentialComputeRequest, ConfidentialComputeResponse}, }; use axum::{extract::State, Extension, Json}; +use hyper::StatusCode; use opentelemetry::KeyValue; use reqwest::Client; use serde_json::Value; @@ -118,28 +121,48 @@ pub async fn image_generations_handler( let model = payload .get(MODEL_KEY) .and_then(|m| m.as_str()) - .unwrap_or("unknown"); + .unwrap_or("unknown") + .to_string(); match handle_image_generations_response( &state, - payload.clone(), + payload, payload_hash, stack_small_id, client_encryption_metadata, &endpoint, timer, - model.to_string(), + model.clone(), ) .await { Ok(response) => { - TOTAL_COMPLETED_REQUESTS.add(1, &[KeyValue::new("model", model.to_owned())]); + TOTAL_COMPLETED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); Ok(response) } Err(e) => { - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new("model", model.to_owned())]); - TOTAL_FAILED_IMAGE_GENERATION_REQUESTS - .add(1, &[KeyValue::new("model", model.to_owned())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + _ => { + TOTAL_FAILED_IMAGE_GENERATION_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + } if let Some(stack_small_id) = stack_small_id { let concurrent_requests = handle_concurrent_requests_count_decrement( &state.concurrent_requests_per_stack, @@ -263,13 +286,13 @@ pub async fn confidential_image_generations_handler( match handle_image_generations_response( &state, - payload.clone(), + payload, payload_hash, stack_small_id, client_encryption_metadata, &endpoint, timer, - model.to_string(), + model.clone(), ) .await { @@ -306,9 +329,28 @@ pub async fn confidential_image_generations_handler( Ok(response) } Err(e) => { - TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new("model", model.clone())]); - TOTAL_FAILED_IMAGE_CONFIDENTIAL_GENERATION_REQUESTS - .add(1, &[KeyValue::new("model", model.clone())]); + match e.status_code() { + StatusCode::TOO_MANY_REQUESTS => { + TOTAL_TOO_MANY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::BAD_REQUEST => { + TOTAL_BAD_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::LOCKED => { + TOTAL_LOCKED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::TOO_EARLY => { + TOTAL_TOO_EARLY_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + StatusCode::UNAUTHORIZED => { + TOTAL_UNAUTHORIZED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + _ => { + TOTAL_FAILED_IMAGE_CONFIDENTIAL_GENERATION_REQUESTS + .add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + TOTAL_FAILED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, model.clone())]); + } + } if let Some(stack_small_id) = stack_small_id { let concurrent_requests = handle_concurrent_requests_count_decrement( &state.concurrent_requests_per_stack, diff --git a/atoma-service/src/handlers/metrics.rs b/atoma-service/src/handlers/metrics.rs index 4497f291..7515e0eb 100644 --- a/atoma-service/src/handlers/metrics.rs +++ b/atoma-service/src/handlers/metrics.rs @@ -346,6 +346,81 @@ pub static TOTAL_FAILED_CHAT_REQUESTS: LazyLock> = LazyLock::new(|| .build() }); +/// Counter metric that tracks the total number of too many requests. +/// +/// # Metric Details +/// - Name: `atoma_total_too_many_requests` +/// - Type: Counter +/// - Labels: `model` +/// - Unit: requests (count) +pub static TOTAL_TOO_MANY_REQUESTS: LazyLock> = LazyLock::new(|| { + GLOBAL_METER + .u64_counter("atoma_total_too_many_requests") + .with_description("Total number of too many requests") + .with_unit("requests") + .build() +}); + +/// Counter metric that tracks the total number of unauthorized requests. +/// +/// # Metric Details +/// - Name: `atoma_total_unauthorized_requests` +/// - Type: Counter +/// - Labels: `model` +/// - Unit: requests (count) +pub static TOTAL_UNAUTHORIZED_REQUESTS: LazyLock> = LazyLock::new(|| { + GLOBAL_METER + .u64_counter("atoma_total_unauthorized_requests") + .with_description("Total number of unauthorized requests") + .with_unit("requests") + .build() +}); + +/// Counter metric that tracks the total number of too early requests. +/// +/// # Metric Details +/// - Name: `atoma_total_too_early_requests` +/// - Type: Counter +/// - Labels: `model` +/// - Unit: requests (count) +pub static TOTAL_TOO_EARLY_REQUESTS: LazyLock> = LazyLock::new(|| { + GLOBAL_METER + .u64_counter("atoma_total_too_early_requests") + .with_description("Total number of too early requests") + .with_unit("requests") + .build() +}); + +/// Counter metric that tracks the total number of locked requests. +/// +/// # Metric Details +/// - Name: `atoma_total_locked_requests` +/// - Type: Counter +/// - Labels: `model` +/// - Unit: requests (count) +pub static TOTAL_LOCKED_REQUESTS: LazyLock> = LazyLock::new(|| { + GLOBAL_METER + .u64_counter("atoma_total_locked_requests") + .with_description("Total number of locked requests") + .with_unit("requests") + .build() +}); + +/// Counter metric that tracks the total number of bad request requests. +/// +/// # Metric Details +/// - Name: `atoma_TOTAL_BAD_REQUESTS` +/// - Type: Counter +/// - Labels: `model` +/// - Unit: requests (count) +pub static TOTAL_BAD_REQUESTS: LazyLock> = LazyLock::new(|| { + GLOBAL_METER + .u64_counter("atoma_TOTAL_BAD_REQUESTS") + .with_description("Total number of bad request requests") + .with_unit("requests") + .build() +}); + /// Counter metric that tracks the total number of confidential chat requests. /// /// # Metric Details From 15e31ba865f9271000aea1484f3b71ec7851ba55 Mon Sep 17 00:00:00 2001 From: Chad Nehemiah Date: Thu, 29 May 2025 04:12:35 -0500 Subject: [PATCH 02/15] ci: use stable toolchain (#645) * ci: use stable toolchain * chore: fix clippy issues --- .github/workflows/ci.yml | 2 +- .github/workflows/coverage.yml | 2 +- Dockerfile | 4 ++-- atoma-daemon/src/components/openapi.rs | 2 +- atoma-p2p-tester/Dockerfile | 4 ++-- atoma-p2p/src/service.rs | 9 ++++++--- atoma-p2p/src/tests.rs | 17 ++++++++++------- atoma-p2p/src/types.rs | 22 +++++++++++----------- atoma-p2p/src/utils.rs | 16 +++++++++------- atoma-service/src/components/openapi.rs | 2 +- atoma-service/src/streamer.rs | 1 + 11 files changed, 45 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42d72a81..9f044761 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ name: CI merge_group: env: - toolchain: nightly-2024-11-14 + toolchain: stable CARGO_HTTP_MULTIPLEXING: false CARGO_TERM_COLOR: always CARGO_UNSTABLE_SPARSE_REGISTRY: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ee37ddd9..089ba8f3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -3,7 +3,7 @@ name: Coverage on: [push, pull_request] env: - toolchain: nightly-2024-11-14 + toolchain: stable CARGO_HTTP_MULTIPLEXING: false CARGO_TERM_COLOR: always CARGO_UNSTABLE_SPARSE_REGISTRY: true diff --git a/Dockerfile b/Dockerfile index cecda550..6aea2fb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,8 +22,8 @@ RUN echo "* soft nofile 65535" >> /etc/security/limits.conf && \ echo "* soft nproc 65535" >> /etc/security/limits.conf && \ echo "* hard nproc 65535" >> /etc/security/limits.conf -# Install Rust 1.84.0 -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.84.0 \ +# Install Rust 1.87.0 +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.87.0 \ && . "$HOME/.cargo/env" # Add cargo to PATH diff --git a/atoma-daemon/src/components/openapi.rs b/atoma-daemon/src/components/openapi.rs index 2f90dd6d..ab879e02 100644 --- a/atoma-daemon/src/components/openapi.rs +++ b/atoma-daemon/src/components/openapi.rs @@ -46,7 +46,7 @@ pub fn openapi_routes() -> Router { let spec_path = docs_dir.join("openapi.yml"); fs::write(&spec_path, spec).expect("Failed to write OpenAPI spec to file"); - println!("OpenAPI spec written to: {spec_path:?}"); + println!("OpenAPI spec written to: {}", spec_path.display()); } Router::new() diff --git a/atoma-p2p-tester/Dockerfile b/atoma-p2p-tester/Dockerfile index 24495bc7..63684040 100644 --- a/atoma-p2p-tester/Dockerfile +++ b/atoma-p2p-tester/Dockerfile @@ -18,8 +18,8 @@ RUN apt-get update && apt-get install -y \ ca-certificates \ && rm -rf /var/lib/apt/lists/* -# Install Rust 1.84.0 -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.84.0 \ +# Install Rust 1.87.0 +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.87.0 \ && . "$HOME/.cargo/env" # Add cargo to PATH diff --git a/atoma-p2p/src/service.rs b/atoma-p2p/src/service.rs index 64a43e25..5117d8be 100644 --- a/atoma-p2p/src/service.rs +++ b/atoma-p2p/src/service.rs @@ -1029,7 +1029,8 @@ impl AtomaP2pNode { return Ok(()); } // Directly deserialize SignedNodeMessage using new method - let signed_node_message = SignedNodeMessage::deserialize_with_signature(&message_data)?; + let signed_node_message = + SignedNodeMessage::deserialize_with_signature(&message_data).map_err(|e| *e)?; let signature_len = signed_node_message.signature.len(); trace!( target = "atoma-p2p", @@ -1058,7 +1059,7 @@ impl AtomaP2pNode { ); // NOTE: We should reject the message if it fails to validate // as it means the node is not being following the current protocol - if let AtomaP2pNodeError::UrlParseError(_) = e { + if let AtomaP2pNodeError::UrlParseError(_) = *e { // We remove the peer from the gossipsub topic, because it is not a valid URL and therefore cannot be reached // by clients for processing OpenAI api compatible AI requests, so these peers are not useful for the network self.swarm @@ -1192,7 +1193,9 @@ impl AtomaP2pNode { node_message, signature: Bytes::copy_from_slice(signature.as_ref()), }; - let serialized_signed_node_message = signed_node_message.serialize_with_signature()?; + let serialized_signed_node_message = signed_node_message + .serialize_with_signature() + .map_err(|e| *e)?; let topic = gossipsub::IdentTopic::new(METRICS_GOSPUBSUB_TOPIC); self.swarm .behaviour_mut() diff --git a/atoma-p2p/src/tests.rs b/atoma-p2p/src/tests.rs index 7f32cebc..0d3e24f2 100644 --- a/atoma-p2p/src/tests.rs +++ b/atoma-p2p/src/tests.rs @@ -161,7 +161,10 @@ async fn test_validate_usage_metrics_message_invalid_url() { &tx, ) .await; - assert!(matches!(result, Err(AtomaP2pNodeError::UrlParseError(_)))); + assert!(matches!( + result, + Err(e) if matches!(*e, AtomaP2pNodeError::UrlParseError(_)) + )); } #[tokio::test] @@ -185,7 +188,7 @@ async fn test_validate_usage_metrics_message_expired_timestamp() { .await; assert!(matches!( result, - Err(AtomaP2pNodeError::InvalidPublicAddressError(_)) + Err(e) if matches!(*e, AtomaP2pNodeError::InvalidPublicAddressError(_)) )); } @@ -210,7 +213,7 @@ async fn test_validate_usage_metrics_message_future_timestamp() { .await; assert!(matches!( result, - Err(AtomaP2pNodeError::InvalidPublicAddressError(_)) + Err(e) if matches!(*e, AtomaP2pNodeError::InvalidPublicAddressError(_)) )); } @@ -244,7 +247,7 @@ async fn test_validate_usage_metrics_message_invalid_signature() { .await; assert!(matches!( result, - Err(AtomaP2pNodeError::SignatureVerificationError(_)) + Err(e) if matches!(*e, AtomaP2pNodeError::SignatureVerificationError(_)) )); } @@ -283,7 +286,7 @@ async fn test_validate_usage_metrics_message_invalid_node_ownership() { assert!(matches!( result, - Err(AtomaP2pNodeError::NodeSmallIdOwnershipVerificationError(_)) + Err(e) if matches!(*e, AtomaP2pNodeError::NodeSmallIdOwnershipVerificationError(_)) )); } @@ -312,7 +315,7 @@ async fn test_validate_usage_metrics_message_state_manager_error() { .await; assert!(matches!( result, - Err(AtomaP2pNodeError::StateManagerError(_)) + Err(e) if matches!(*e, AtomaP2pNodeError::StateManagerError(_)) )); } @@ -352,6 +355,6 @@ async fn test_validate_usage_metrics_message_response_channel_error() { .await; assert!(matches!( result, - Err(AtomaP2pNodeError::NodeSmallIdOwnershipVerificationError(_)) + Err(e) if matches!(*e, AtomaP2pNodeError::NodeSmallIdOwnershipVerificationError(_)) )); } diff --git a/atoma-p2p/src/types.rs b/atoma-p2p/src/types.rs index 99d3d81b..31a95310 100644 --- a/atoma-p2p/src/types.rs +++ b/atoma-p2p/src/types.rs @@ -17,7 +17,7 @@ pub const SECP256K1_SIGNATURE_LENGTH: usize = 98; /// see pub const SECP256R1_SIGNATURE_LENGTH: usize = 98; -type Result = std::result::Result; +type Result> = std::result::Result; /// An enum representing different types of events that can be emitted by the Atoma P2P node. pub enum AtomaP2pEvent { @@ -138,8 +138,7 @@ pub struct NodeMessage { impl SerializeWithHash for NodeMessage { fn serialize_with_hash(&self) -> Result { let mut buffer = BytesMut::new(); - ciborium::into_writer(self, (&mut buffer).writer()) - .map_err(AtomaP2pNodeError::UsageMetricsSerializeError)?; + ciborium::into_writer(self, (&mut buffer).writer()).map_err(|e| Box::new(e.into()))?; Ok(SerializedMessage { hash: blake3::hash(buffer.as_ref()), message: buffer.freeze(), @@ -185,35 +184,36 @@ pub trait SerializeWithSignature { } impl SerializeWithSignature for SignedNodeMessage { - fn serialize_with_signature(&self) -> Result { + fn serialize_with_signature(&self) -> Result { let mut buffer = BytesMut::with_capacity(1024); buffer.extend_from_slice(&self.signature); // Serialize node message ciborium::into_writer(&self.node_message, (&mut buffer).writer()) - .map_err(AtomaP2pNodeError::UsageMetricsSerializeError)?; + .map_err(|e| Box::new(e.into()))?; Ok(buffer.freeze()) } - fn deserialize_with_signature(data: &[u8]) -> Result { + fn deserialize_with_signature(data: &[u8]) -> Result { let signature_len = data .first() .map(|&flag| match flag { f if f == Ed25519SuiSignature::SCHEME.flag() => Ok(ED25519_SIGNATURE_LENGTH), f if f == Secp256k1SuiSignature::SCHEME.flag() => Ok(SECP256K1_SIGNATURE_LENGTH), f if f == Secp256r1SuiSignature::SCHEME.flag() => Ok(SECP256R1_SIGNATURE_LENGTH), - f => Err(AtomaP2pNodeError::SignatureParseError(format!( + f => Err(Box::new(AtomaP2pNodeError::SignatureParseError(format!( "Invalid signature scheme, expected 0x00, 0x01 or 0x02, received {f:#04x}", - ))), + )))), }) .ok_or_else(|| { - AtomaP2pNodeError::SignatureParseError( + Box::new(AtomaP2pNodeError::SignatureParseError( "Invalid signature scheme: the data is empty".to_string(), - ) + )) })??; let signature = Bytes::copy_from_slice(&data[0..signature_len]); - let node_message = ciborium::from_reader(&data[signature_len..])?; + let node_message = + ciborium::from_reader(&data[signature_len..]).map_err(|e| Box::new(e.into()))?; Ok(Self { node_message, signature, diff --git a/atoma-p2p/src/utils.rs b/atoma-p2p/src/utils.rs index 21774be5..1c726462 100644 --- a/atoma-p2p/src/utils.rs +++ b/atoma-p2p/src/utils.rs @@ -75,7 +75,7 @@ const EXPIRED_TIMESTAMP_THRESHOLD: u64 = 10 * 60; // 10 minutes #[instrument(level = "debug", skip_all)] pub fn validate_node_message_country_url_timestamp( node_message: &NodeMessage, -) -> Result<(), AtomaP2pNodeError> { +) -> Result<(), Box> { let now = std::time::Instant::now().elapsed().as_secs(); let country = node_message.node_metadata.country.as_str(); @@ -91,7 +91,7 @@ pub fn validate_node_message_country_url_timestamp( "Invalid URL format, received address: {}", node_message.node_metadata.node_public_url ); - AtomaP2pNodeError::UrlParseError(e) + Box::new(AtomaP2pNodeError::UrlParseError(e)) })?; // Check if the timestamp is within a reasonable time frame @@ -105,18 +105,20 @@ pub fn validate_node_message_country_url_timestamp( node_message.node_metadata.timestamp, now ); - return Err(AtomaP2pNodeError::InvalidPublicAddressError( + return Err(Box::new(AtomaP2pNodeError::InvalidPublicAddressError( "Timestamp is too far in the past".to_string(), - )); + ))); } Ok(()) } /// Custom validation function for ISO 3166-1 alpha-2 country codes -fn validate_country_code(code: &str) -> Result<(), AtomaP2pNodeError> { +fn validate_country_code(code: &str) -> Result<(), Box> { isocountry::CountryCode::for_alpha2(code).map_err(|_| { - AtomaP2pNodeError::InvalidCountryCodeError("Country code is invalid.".to_string()) + Box::new(AtomaP2pNodeError::InvalidCountryCodeError( + "Country code is invalid.".to_string(), + )) })?; Ok(()) } @@ -331,7 +333,7 @@ pub async fn validate_signed_node_message( node_message_hash: &[u8; 32], signature: &[u8], state_manager_sender: &Sender, -) -> Result<(), AtomaP2pNodeError> { +) -> Result<(), Box> { // Validate the message's node public URL and timestamp validate_node_message_country_url_timestamp(node_message)?; // Verify the signature of the message diff --git a/atoma-service/src/components/openapi.rs b/atoma-service/src/components/openapi.rs index c963eb0a..8887dfd6 100644 --- a/atoma-service/src/components/openapi.rs +++ b/atoma-service/src/components/openapi.rs @@ -62,7 +62,7 @@ pub fn openapi_routes() -> Router { let spec_path = docs_dir.join("openapi.yml"); fs::write(&spec_path, spec).expect("Failed to write OpenAPI spec to file"); - println!("OpenAPI spec written to: {:?}", spec_path); + println!("OpenAPI spec written to: {}", spec_path.display()); } Router::new() diff --git a/atoma-service/src/streamer.rs b/atoma-service/src/streamer.rs index abe26f78..9ded19e1 100644 --- a/atoma-service/src/streamer.rs +++ b/atoma-service/src/streamer.rs @@ -134,6 +134,7 @@ pub struct Streamer { /// kills the connection before the final chunk is sent. If, instead, /// the last chunk is handled, the value is updated to the actual number of tokens /// returned by the LLM inference service + #[allow(clippy::struct_field_names)] streamer_computed_num_tokens: i64, /// The number of input tokens for the request num_input_tokens: i64, From 175854148a80c40f615e9eeaa45a01e152adeb24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Ant=C3=B3nio?= Date: Fri, 30 May 2025 14:40:12 +0100 Subject: [PATCH 03/15] revert to use prometheus for queued requests (#646) * revert to use prometheus for queued requests * add start metrics collector * update logs --- atoma-bin/atoma_node.rs | 18 +- atoma-service/src/config.rs | 8 +- .../src/handlers/chat_completions.rs | 33 +- atoma-service/src/handlers/completions.rs | 34 +- atoma-service/src/handlers/mod.rs | 478 +++++++++++++++++- atoma-service/src/handlers/request_counter.rs | 73 --- atoma-service/src/server.rs | 6 +- atoma-service/src/streamer.rs | 12 - atoma-service/src/tests.rs | 3 +- config.example.toml | 8 - 10 files changed, 491 insertions(+), 182 deletions(-) delete mode 100644 atoma-service/src/handlers/request_counter.rs diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index b8103256..6359881d 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -4,9 +4,7 @@ use anyhow::{Context, Result}; use atoma_confidential::AtomaConfidentialCompute; use atoma_daemon::{telemetry, AtomaDaemonConfig, DaemonState}; use atoma_p2p::{AtomaP2pNode, AtomaP2pNodeConfig}; -use atoma_service::{ - config::AtomaServiceConfig, handlers::request_counter::RequestCounter, server::AppState, -}; +use atoma_service::{config::AtomaServiceConfig, server::AppState}; use atoma_state::{config::AtomaStateManagerConfig, AtomaState, AtomaStateManager}; use atoma_sui::{client::Client, config::Config, subscriber::Subscriber}; use atoma_utils::spawn_with_shutdown; @@ -373,9 +371,21 @@ async fn main() -> Result<()> { keystore: Arc::new(keystore), address_index, whitelist_sui_addresses_for_fiat: config.service.whitelist_sui_addresses_for_fiat, - running_num_requests: Arc::new(RequestCounter::new()), }; + let chat_completions_service_urls = app_state + .chat_completions_service_urls + .iter() + .flat_map(|(model, urls)| { + urls.iter() + .map(|(url, job)| (model.clone(), url.clone(), job.clone())) + }) + .collect(); + atoma_service::handlers::inference_service_metrics::start_metrics_updater( + chat_completions_service_urls, + config.service.metrics_update_interval, + ); + let daemon_app_state = DaemonState { atoma_state: AtomaState::new_from_url(&config.state.database_url).await?, client, diff --git a/atoma-service/src/config.rs b/atoma-service/src/config.rs index 9538fb49..f7544506 100644 --- a/atoma-service/src/config.rs +++ b/atoma-service/src/config.rs @@ -9,12 +9,10 @@ use serde::Deserialize; /// including URLs for various services and a list of models. #[derive(Debug, Deserialize)] pub struct AtomaServiceConfig { - /// URL for the chat completions service with maximum concurrency settings. + /// URL for the chat completions service. /// - /// This is an optional field that, if provided, specifies the endpoint - /// for the chat completions service used by the Atoma Service, together with its - /// associated Prometheus job name. - pub chat_completions_service_urls: HashMap>, + /// This field specifies the endpoint for the chat completions service used by the Atoma Service. + pub chat_completions_service_urls: HashMap>, /// URL for the embeddings service. /// diff --git a/atoma-service/src/handlers/chat_completions.rs b/atoma-service/src/handlers/chat_completions.rs index 7a2815d0..89ec218f 100644 --- a/atoma-service/src/handlers/chat_completions.rs +++ b/atoma-service/src/handlers/chat_completions.rs @@ -49,10 +49,7 @@ use tracing::{debug, info, instrument}; use utoipa::OpenApi; use serde::Deserialize; -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; use crate::{ error::AtomaServiceError, @@ -904,16 +901,12 @@ async fn handle_streaming_response( } })?; let (chat_completions_service_url, status_code) = - get_best_available_chat_completions_service_url( - &state.running_num_requests, - chat_completions_service_urls, - model, - ) - .await - .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { - message: e.to_string(), - endpoint: endpoint.clone(), - })?; + get_best_available_chat_completions_service_url(chat_completions_service_urls, model) + .await + .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { + message: e.to_string(), + endpoint: endpoint.clone(), + })?; if status_code == StatusCode::TOO_MANY_REQUESTS { return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), @@ -931,9 +924,6 @@ async fn handle_streaming_response( .send() .await .map_err(|e| { - state - .running_num_requests - .decrement(&chat_completions_service_url); AtomaServiceError::InternalError { message: format!( "Error sending request to inference service, for request with payload hash: {:?}, and stack small id: {:?}, with error: {}", @@ -946,9 +936,6 @@ async fn handle_streaming_response( })?; if !response.status().is_success() { - state - .running_num_requests - .decrement(&chat_completions_service_url); let status = response.status(); let bytes = response .bytes() @@ -1002,8 +989,6 @@ async fn handle_streaming_response( price_per_one_million_compute_units, user_id, user_address, - Arc::clone(&state.running_num_requests), - chat_completions_service_url, )) .keep_alive( axum::response::sse::KeepAlive::new() @@ -1335,7 +1320,6 @@ pub mod utils { })?; let (chat_completions_service_url, status_code) = get_best_available_chat_completions_service_url( - &state.running_num_requests, chat_completions_service_url_services, model, ) @@ -1358,9 +1342,6 @@ pub mod utils { .json(&payload) .send() .await; - state - .running_num_requests - .decrement(&chat_completions_service_url); let response = response.map_err(|e| { AtomaServiceError::InternalError { message: format!( diff --git a/atoma-service/src/handlers/completions.rs b/atoma-service/src/handlers/completions.rs index 268d4e4e..e3bbc3b5 100644 --- a/atoma-service/src/handlers/completions.rs +++ b/atoma-service/src/handlers/completions.rs @@ -37,10 +37,7 @@ use tracing::{debug, info, instrument}; use utoipa::OpenApi; use serde::Deserialize; -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; use crate::{ error::AtomaServiceError, @@ -877,16 +874,13 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), } })?; - let (completions_service_url, status_code) = get_best_available_chat_completions_service_url( - &state.running_num_requests, - chat_completions_service_urls, - model, - ) - .await - .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { - message: e.to_string(), - endpoint: endpoint.clone(), - })?; + let (completions_service_url, status_code) = + get_best_available_chat_completions_service_url(chat_completions_service_urls, model) + .await + .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { + message: e.to_string(), + endpoint: endpoint.clone(), + })?; if status_code == StatusCode::TOO_MANY_REQUESTS { return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), @@ -901,9 +895,6 @@ async fn handle_streaming_response( .send() .await .map_err(|e| { - state - .running_num_requests - .decrement(&completions_service_url); AtomaServiceError::InternalError { message: format!( "Error sending request to inference service, for request with payload hash: {:?}, and stack small id: {:?}, with error: {}", @@ -916,9 +907,6 @@ async fn handle_streaming_response( })?; if !response.status().is_success() { - state - .running_num_requests - .decrement(&completions_service_url); let status = response.status(); let bytes = response .bytes() @@ -971,8 +959,6 @@ async fn handle_streaming_response( price_per_one_million_tokens, user_id, user_address, - Arc::clone(&state.running_num_requests), - completions_service_url, )) .keep_alive( axum::response::sse::KeepAlive::new() @@ -1297,7 +1283,6 @@ pub mod utils { })?; let (completions_service_url, status_code) = get_best_available_chat_completions_service_url( - &state.running_num_requests, completions_service_url_services, model, ) @@ -1317,9 +1302,6 @@ pub mod utils { .json(&payload) .send() .await; - state - .running_num_requests - .decrement(&completions_service_url); let response = response .map_err(|e| { AtomaServiceError::InternalError { diff --git a/atoma-service/src/handlers/mod.rs b/atoma-service/src/handlers/mod.rs index 7b86d750..aa79a7e4 100644 --- a/atoma-service/src/handlers/mod.rs +++ b/atoma-service/src/handlers/mod.rs @@ -4,7 +4,6 @@ pub mod completions; pub mod embeddings; pub mod image_generations; pub mod metrics; -pub mod request_counter; pub mod request_model; pub mod stop_streamer; @@ -572,14 +571,268 @@ pub fn handle_status_code_error( } pub mod inference_service_metrics { + use futures::future::join_all; + use opentelemetry::KeyValue; + use prometheus_parse::Scrape; + use prometheus_parse::Value; + use rand::Rng; + use std::sync::Arc; + use std::sync::LazyLock; + use std::time::Duration; + use tokio::sync::RwLock; + use tokio::time; + use crate::handlers::metrics::CHAT_COMPLETIONS_TOO_MANY_REQUESTS; use hyper::StatusCode; - use rand::seq::SliceRandom; - use tracing::instrument; + use tracing::{info, instrument}; - use super::request_counter::RequestCounter; + use super::InferenceService; pub type Result = std::result::Result; + type MetricValue = ChatCompletionsMetrics; + type MetricResult = Result; + type MetricsVec = Vec; + type CachedMetrics = Option; + type MetricsLock = Arc>; + + /// The default interval for updating the metrics + const DEFAULT_METRICS_UPDATE_INTERVAL_MILLIS: u64 = 35; + + /// The timeout for the Prometheus metrics queries + const METRICS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2); + + /// The HTTP client for the metrics queries + static HTTP_CLIENT: LazyLock = LazyLock::new(|| { + reqwest::Client::builder() + .timeout(METRICS_TIMEOUT) + .build() + .expect("Failed to create HTTP client") + }); + + /// Chat completions metrics + #[derive(Debug, Clone)] + struct ChatCompletionsMetrics { + /// The model name + model: String, + /// The chat completions service url + chat_completions_service_url: String, + /// The number of queue requests + num_queued_requests: f64, + /// The number of running requests + num_running_requests: f64, + } + + /// Cache structure to store metrics + #[derive(Debug, Default)] + struct MetricsCache { + metrics: MetricsLock, + } + + impl MetricsCache { + fn new() -> Self { + Self { + metrics: Arc::new(RwLock::new(None)), + } + } + + async fn get_metrics(&self) -> Option { + self.metrics.read().await.clone() + } + + async fn update_metrics(&self, new_metrics: Vec>) { + *self.metrics.write().await = Some(new_metrics); + } + } + + /// Global metrics cache + #[allow(clippy::redundant_closure)] + static VLLM_METRICS_CACHE: LazyLock = LazyLock::new(|| MetricsCache::new()); + + /// Global metrics cache + #[allow(clippy::redundant_closure)] + static SGLANG_METRICS_CACHE: LazyLock = LazyLock::new(|| MetricsCache::new()); + + /// Start the background task to update metrics every 500 milliseconds + /// + /// # Arguments + /// + /// * `chat_completions_service_urls` - A vector of tuples containing the model name, the chat completions service URL and the job name. + /// * `metrics_update_interval` - The interval in seconds to update the metrics. + #[instrument(level = "info", skip_all)] + pub fn start_metrics_updater( + chat_completions_service_urls: Vec<(String, String, String)>, + metrics_update_interval: Option, + ) { + type ChatCompletionsServiceUrls = Vec<(String, String, String)>; + info!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "Starting metrics updater with {chat_completions_service_urls:?}" + ); + let (vllm_chat_completions_service_urls, sglang_chat_completions_service_urls): ( + ChatCompletionsServiceUrls, + ChatCompletionsServiceUrls, + ) = chat_completions_service_urls + .iter() + .cloned() + .partition(|(_, _, job)| job.contains("vllm")); + info!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "Partitioned chat completions service urls: vllm: {vllm_chat_completions_service_urls:?}, sglang: {sglang_chat_completions_service_urls:?}" + ); + let vllm_chat_completions_service_urls = Arc::new(vllm_chat_completions_service_urls); + let sglang_chat_completions_service_urls = Arc::new(sglang_chat_completions_service_urls); + tokio::spawn(async move { + let metrics_interval = + metrics_update_interval.unwrap_or(DEFAULT_METRICS_UPDATE_INTERVAL_MILLIS); + info!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "Metrics update interval: {metrics_interval} milliseconds" + ); + let mut interval = time::interval(Duration::from_millis(metrics_interval)); + loop { + interval.tick().await; + if !vllm_chat_completions_service_urls.is_empty() { + let vllm_metrics = + get_metrics(&InferenceService::Vllm, &vllm_chat_completions_service_urls) + .await; + if vllm_metrics.iter().any(std::result::Result::is_ok) { + VLLM_METRICS_CACHE.update_metrics(vllm_metrics).await; + } else { + tracing::warn!( + "Failed to retrieve any valid vLLM metrics, not updating cache" + ); + } + } + if !sglang_chat_completions_service_urls.is_empty() { + let sglang_metrics = get_metrics( + &InferenceService::SgLang, + &sglang_chat_completions_service_urls, + ) + .await; + if sglang_metrics.iter().any(std::result::Result::is_ok) { + SGLANG_METRICS_CACHE.update_metrics(sglang_metrics).await; + } else { + tracing::warn!( + "Failed to retrieve any valid SgLang metrics, not updating cache" + ); + } + } + } + }); + } + + /// Fetches metrics from the specified chat completions service URL. + /// + /// This function retrieves metrics from the specified chat completions service URL + /// and parses the response to extract relevant metrics such as the number of queue + /// requests and running requests. It handles errors gracefully and returns a vector + /// of results, where each result contains the metrics for a specific service URL. + /// + /// # Arguments + /// + /// * `inference_service` - The inference service type (vLLM or SgLang). + /// * `jobs_with_url` - A slice of tuples containing model name, the chat completions service URL + /// and the job name (e.g., "vllm-service", "sglang-service"). + /// + /// # Returns + /// + /// Returns a `Vec>`, where each result contains + /// the metrics for a specific service URL. If an error occurs while fetching or parsing + /// the metrics, the error is returned in the result. + /// + /// # Errors + /// + /// * `ChatCompletionsMetricsError::NoMetricsFound`: If no metrics are found for the + /// specified job or if the metrics response is invalid. + /// * Other variants of `ChatCompletionsMetricsError` may be returned if underlying + /// issues occur during metric collection from Prometheus (e.g., network errors, + /// parsing errors), though the function attempts to handle missing individual metrics + /// gracefully. + async fn get_metrics( + inference_service: &InferenceService, + jobs_with_url: &[(String, String, String)], // (model, url, job) + ) -> Vec> { + let tasks = + jobs_with_url + .iter() + .map(|(model, chat_completions_service_url, job)| async move { + let response = HTTP_CLIENT + .get(format!("{chat_completions_service_url}/metrics")) + .send() + .await + .map_err(|_| { + ChatCompletionsMetricsError::NoMetricsFound(job.to_string()) + })?; + let body = response.text().await?; + let lines = body + .lines() + .map(|line| Ok(line.replace(inference_service.get_service_prefix(), ""))); + let metrics = Scrape::parse(lines).unwrap(); + let num_queued_requests = extract_metric( + &metrics, + inference_service.get_queued_requests_metric_name(), + job, + )?; + let num_running_requests = extract_metric( + &metrics, + inference_service.get_running_requests_metric_name(), + job, + )?; + + Ok(ChatCompletionsMetrics { + model: model.clone(), + chat_completions_service_url: chat_completions_service_url.clone(), + num_queued_requests, + num_running_requests, + }) + }); + join_all(tasks).await + } + + /// Extracts a specific metric from the Scrape response. + /// + /// This function searches for a metric with the specified name in the + /// Scrape response and returns its value if found. + /// + /// # Arguments + /// + /// * `metrics` - The Scrape response containing the metrics. + /// * `name` - The name of the metric to extract. + /// * `job` - The job name used for error reporting. + /// + /// # Returns + /// + /// Returns a `Result` containing the metric value if found, + /// or an error if not found or if the value is not a Gauge. + /// + /// # Errors + /// + /// * `ChatCompletionsMetricsError::NoMetricsFound`: If the specified metric is not found + /// or if the value is not a Gauge. + /// * Other variants of `ChatCompletionsMetricsError` may be returned if underlying + /// issues occur during metric collection from Prometheus (e.g., network errors, + /// parsing errors), + /// though the function attempts to handle missing individual metrics gracefully. + fn extract_metric(metrics: &Scrape, name: &str, job: &str) -> Result { + metrics + .samples + .iter() + .find(|s| s.metric == name) + .ok_or_else(|| ChatCompletionsMetricsError::NoMetricsFound(job.to_string())) + .and_then(|sample| { + if let Value::Gauge(value) = sample.value { + Ok(value) + } else { + Err(ChatCompletionsMetricsError::NoMetricsFound(job.to_string())) + } + }) + } /// Selects the best available chat completions service URL for a given model based on performance metrics. /// @@ -642,41 +895,224 @@ pub mod inference_service_metrics { #[instrument(level = "info", skip_all, fields(model=model))] #[allow(clippy::float_cmp)] pub async fn get_best_available_chat_completions_service_url( - running_num_requests: &RequestCounter, - chat_completions_service_urls: &[(String, String, usize)], // (url, job, max_concurrent_requests) + chat_completions_service_urls: &[(String, String)], model: &str, ) -> Result<(String, StatusCode)> { - // Ensure there are service URLs to choose from. + const MAX_ALLOWED_NUM_QUEUED_REQUESTS: f64 = 1.0; // Default to 1 request + + type ChatCompletionsServiceUrls = Vec<(String, String)>; + if chat_completions_service_urls.is_empty() { - tracing::warn!( - target = "atoma-service", - model = model, - "No chat completions service URLs provided for model." - ); return Err( ChatCompletionsMetricsError::NoChatCompletionsServiceUrlsFound(model.to_string()), ); } - let mut shuffled_chat_completions_service_urls = chat_completions_service_urls.to_vec(); - shuffled_chat_completions_service_urls.shuffle(&mut rand::thread_rng()); - for (url_str, _job_name, max_concurrent_val) in &shuffled_chat_completions_service_urls { - if running_num_requests.increment(url_str, *max_concurrent_val) { - return Ok((url_str.clone(), StatusCode::OK)); + tracing::debug!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "Getting best available chat completions service URL for model: {model} and urls: {chat_completions_service_urls:?}" + ); + let (vllm_chat_completions_service_urls, sglang_chat_completions_service_urls): ( + ChatCompletionsServiceUrls, + ChatCompletionsServiceUrls, + ) = chat_completions_service_urls + .iter() + .cloned() + .partition(|(_, job)| job.contains("vllm")); + + tracing::debug!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "Partitioned chat completions service urls: vllm: {vllm_chat_completions_service_urls:?}, sglang: {sglang_chat_completions_service_urls:?}" + ); + + // Get cached metrics + let vllm_metrics = if vllm_chat_completions_service_urls.is_empty() { + vec![] + } else if let Some(metrics) = VLLM_METRICS_CACHE.get_metrics().await { + metrics + } else { + info!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "No cached vLLM metrics, getting them directly" + ); + let vllm_chat_completions_service_urls_with_model: Vec<(String, String, String)> = + vllm_chat_completions_service_urls + .iter() + .map(|(url, job)| (model.to_string(), url.clone(), job.clone())) + .collect(); + get_metrics( + &InferenceService::Vllm, + &vllm_chat_completions_service_urls_with_model, + ) + .await + }; + let sglang_metrics = if sglang_chat_completions_service_urls.is_empty() { + vec![] + } else if let Some(metrics) = SGLANG_METRICS_CACHE.get_metrics().await { + metrics + } else { + info!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "No cached SgLang metrics, getting them directly" + ); + let sglang_chat_completions_service_urls_with_model: Vec<(String, String, String)> = + sglang_chat_completions_service_urls + .iter() + .map(|(url, job)| (model.to_string(), url.clone(), job.clone())) + .collect(); + get_metrics( + &InferenceService::SgLang, + &sglang_chat_completions_service_urls_with_model, + ) + .await + }; + + tracing::debug!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "Received vLLM metrics: {vllm_metrics:?}, SgLang metrics: {sglang_metrics:?}" + ); + + let mut metrics_results = Vec::new(); + for metric in vllm_metrics.into_iter().chain(sglang_metrics.into_iter()) { + match metric { + Ok(ChatCompletionsMetrics { + model: current_model, + chat_completions_service_url, + num_queued_requests, + num_running_requests, + }) => { + tracing::info!( + target = "atoma-service", + module = "inference_service_metrics", + level = "info", + "current_model = {current_model}, model = {model}, they are equal = {}", + current_model == model + ); + if current_model.to_lowercase() != model.to_lowercase() { + // NOTE: We only want to consider metrics for the current model + continue; + } + info!( + target = "atoma-service", + module = "vllm_metrics", + level = "info", + "Received vLLM/SgLang metrics response for {chat_completions_service_url}:\n + num_queued_requests={num_queued_requests}, + num_running_requests={num_running_requests}" + ); + metrics_results.push(ChatCompletionsMetrics { + model: current_model, + chat_completions_service_url, + num_queued_requests, + num_running_requests, + }); + } + Err(e) => { + tracing::warn!( + target = "atoma-service", + module = "vllm_metrics", + level = "error", + "Failed to get metrics for chat completions service url with error: {e}", + ); + } } } - tracing::warn!( + if metrics_results.is_empty() { + tracing::warn!( + target = "atoma-service", + level = "warn", + "No metrics found for model: {model}", + ); + // NOTE: In this case, we pick one of the urls at random + let random_index = rand::thread_rng().gen_range(0..chat_completions_service_urls.len()); + let best_url = chat_completions_service_urls[random_index].0.clone(); + return Ok((best_url, StatusCode::OK)); + } + + // Select the best available chat completions service URL based on the number of queued and running requests. + let best_metrics = metrics_results + .iter() + .min_by_key(|metric| { + ( + metric.num_queued_requests as i64, + metric.num_running_requests as i64, + ) + }) + .unwrap(); + + if best_metrics.num_queued_requests >= MAX_ALLOWED_NUM_QUEUED_REQUESTS { + tracing::warn!( + target = "atoma-service", + level = "warn", + "Node is currently under high load, the best available chat completions service URL for model: {model} has a num queue requests of at least {} requests", + best_metrics.num_queued_requests + ); + CHAT_COMPLETIONS_TOO_MANY_REQUESTS.add(1, &[KeyValue::new("model", model.to_string())]); + return Ok(( + chat_completions_service_urls[0].0.clone(), + StatusCode::TOO_MANY_REQUESTS, + )); + } + + let best_url = best_metrics.chat_completions_service_url.clone(); + tracing::info!( target = "atoma-service", - model = model, - "No chat completions service URLs below max capacity found, returning TOO_MANY_REQUESTS status." + level = "info", + "Best available chat completions service URL for model: {model} is: {best_url} with and {} queue requests", + best_metrics.num_queued_requests ); - return Ok((String::new(), StatusCode::TOO_MANY_REQUESTS)); + Ok((best_url, StatusCode::OK)) } #[derive(Debug, thiserror::Error, Clone)] pub enum ChatCompletionsMetricsError { + #[error("Failed to get metrics: {0}")] + GetMetricsError(String), #[error("No chat completions service urls found for model: {0}")] NoChatCompletionsServiceUrlsFound(String), + #[error("Invalid metrics value: {0}")] + InvalidMetricsValue(String), + #[error("Invalid metrics response: {0}")] + InvalidMetricsResponse(String), + #[error("Failed to create HTTP client: {0}")] + FailedToCreateHttpClient(String), + #[error("No metrics found for job: {0}")] + NoMetricsFound(String), + } + + // From implementations to handle conversions from error types to our cloneable error type + impl From for ChatCompletionsMetricsError { + fn from(err: reqwest::Error) -> Self { + Self::GetMetricsError(err.to_string()) + } + } + + impl From for ChatCompletionsMetricsError { + fn from(err: std::num::ParseFloatError) -> Self { + Self::InvalidMetricsValue(err.to_string()) + } + } + + impl From for ChatCompletionsMetricsError { + fn from(err: serde_json::Error) -> Self { + Self::InvalidMetricsResponse(err.to_string()) + } + } + + impl From for ChatCompletionsMetricsError { + fn from(err: prometheus_http_query::Error) -> Self { + Self::FailedToCreateHttpClient(err.to_string()) + } } } diff --git a/atoma-service/src/handlers/request_counter.rs b/atoma-service/src/handlers/request_counter.rs deleted file mode 100644 index 73444fca..00000000 --- a/atoma-service/src/handlers/request_counter.rs +++ /dev/null @@ -1,73 +0,0 @@ -use atoma_p2p::metrics::RUNNING_REQUESTS; -use dashmap::{DashMap, Entry}; -use opentelemetry::KeyValue; -use tracing::error; - -/// A thread-safe request counter that tracks the number of requests being processed for each inference service. -#[derive(Clone, Debug)] -pub struct RequestCounter { - /// A map that holds the count of running requests for each inference service. - running_num_requests: DashMap, -} - -impl Default for RequestCounter { - fn default() -> Self { - Self::new() - } -} - -impl RequestCounter { - /// Creates a new instance of `RequestCounter`. - #[must_use] - pub fn new() -> Self { - Self { - running_num_requests: DashMap::new(), - } - } - - /// Increments the count for the given key or initializes it to 1 if it does not exist. - pub fn increment(&self, key: &str, max_value: usize) -> bool { - let mut entry = self - .running_num_requests - .entry(key.to_string()) - .or_insert(0); - if *entry >= max_value { - false - } else { - *entry += 1; - RUNNING_REQUESTS.record(*entry as u64, &[KeyValue::new("service", key.to_string())]); - true - } - } - - /// Decrements the count for the given key. If the count reaches zero, the entry is removed. - pub fn decrement(&self, key: &str) { - match self.running_num_requests.entry(key.to_string()) { - Entry::Occupied(mut entry) => { - let count = entry.get_mut(); - *count -= 1; - RUNNING_REQUESTS - .record(*count as u64, &[KeyValue::new("service", key.to_string())]); - if *count == 0 { - entry.remove(); - } - } - Entry::Vacant(_) => { - // This should not happen - error!( - target = "atoma-service", - level = "info", - event = "chat-completions-handler", - "Attempted to decrement a non-existent key: {}", - key - ); - } - } - } - - /// Retrieves the current count for the given key. - #[must_use] - pub fn get_count(&self, key: &str) -> usize { - self.running_num_requests.get(key).map_or(0, |entry| *entry) - } -} diff --git a/atoma-service/src/server.rs b/atoma-service/src/server.rs index 7f4638f6..0db7e1bc 100644 --- a/atoma-service/src/server.rs +++ b/atoma-service/src/server.rs @@ -54,7 +54,6 @@ use crate::{ confidential_image_generations_handler, image_generations_handler, CONFIDENTIAL_IMAGE_GENERATIONS_PATH, IMAGE_GENERATIONS_PATH, }, - request_counter::RequestCounter, stop_streamer::stop_streamer_handler, }, middleware::{ @@ -175,7 +174,7 @@ pub struct AppState { /// These URLs point to the external services responsible for performing /// AI model chat completions. The application forwards requests to this /// service to obtain AI-generated responses. - pub chat_completions_service_urls: HashMap>, + pub chat_completions_service_urls: HashMap>, /// URL for the embeddings service. /// @@ -205,9 +204,6 @@ pub struct AppState { /// The Sui address of the clients that are allowed to use fiat. pub whitelist_sui_addresses_for_fiat: Vec, - - /// Number of running requests for each inference service. - pub running_num_requests: Arc, } /// Creates and configures the main router for the application. diff --git a/atoma-service/src/streamer.rs b/atoma-service/src/streamer.rs index 9ded19e1..bb1ffc2b 100644 --- a/atoma-service/src/streamer.rs +++ b/atoma-service/src/streamer.rs @@ -31,7 +31,6 @@ use crate::{ CHAT_COMPLETIONS_STREAMING_LATENCY_METRICS, CHAT_COMPLETIONS_TIME_TO_FIRST_TOKEN, TOTAL_COMPLETED_REQUESTS, }, - request_counter::RequestCounter, update_fiat_amount, update_stack_num_compute_units, USAGE_KEY, }, server::utils, @@ -144,10 +143,6 @@ pub struct Streamer { user_id: Option, /// The user address for the request user_address: String, - /// A map to keep track of the number of requests currently being processed - running_num_requests: Arc, - /// The URL of the chat completions service - chat_completions_service_url: String, } /// Represents the various states of a streaming process @@ -185,8 +180,6 @@ impl Streamer { price_per_one_million_tokens: i64, user_id: Option, user_address: String, - running_num_requests: Arc, - chat_completions_service_url: String, ) -> Self { Self { concurrent_requests, @@ -213,8 +206,6 @@ impl Streamer { price_per_one_million_tokens, user_id, user_address, - running_num_requests, - chat_completions_service_url, } } @@ -914,9 +905,6 @@ impl Drop for Streamer { ) )] fn drop(&mut self) { - self.running_num_requests - .decrement(&self.chat_completions_service_url); - if self.is_final_chunk_handled || matches!(self.status, StreamStatus::Failed(_)) { TOTAL_COMPLETED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, self.model.clone())]); return; diff --git a/atoma-service/src/tests.rs b/atoma-service/src/tests.rs index c5f726cc..c006dbb5 100644 --- a/atoma-service/src/tests.rs +++ b/atoma-service/src/tests.rs @@ -34,7 +34,7 @@ mod middleware { use crate::{ handlers::{ chat_completions::CHAT_COMPLETIONS_PATH, embeddings::EMBEDDINGS_PATH, - image_generations::IMAGE_GENERATIONS_PATH, request_counter::RequestCounter, + image_generations::IMAGE_GENERATIONS_PATH, }, middleware::{ confidential_compute_middleware, signature_verification_middleware, verify_permissions, @@ -341,7 +341,6 @@ mod middleware { address_index: 0, stack_retrieve_sender, whitelist_sui_addresses_for_fiat: vec![], - running_num_requests: Arc::new(RequestCounter::new()), }, public_key, signature, diff --git a/config.example.toml b/config.example.toml index afbc3765..da176dac 100644 --- a/config.example.toml +++ b/config.example.toml @@ -3,42 +3,34 @@ chat_completions_service_urls = { "Infermatic/Llama-3.3-70B-Instruct-FP8-Dynamic [ "http://chat-completions1:8000", "vllm1", - 256, ], [ "http://chat-completions2:8000", "vllm2", - 256, ], [ "http://chat-completions3:8000", "vllm3", - 256, ], [ "http://chat-completions4:8000", "vllm4", - 256, ], [ "http://chat-completions5:8000", "vllm5", - 256, ], [ "http://chat-completions6:8000", "vllm6", - 256, ], [ "http://chat-completions7:8000", "vllm7", - 256, ], [ "http://chat-completions8:8000", "vllm8", - 256, ], ] } embeddings_service_url = "http://embeddings:80" From dee70b771715d037a46ac357626605cca0b0d7cf Mon Sep 17 00:00:00 2001 From: Martin Stefcek <35243812+Cifko@users.noreply.github.com> Date: Fri, 30 May 2025 16:14:41 +0200 Subject: [PATCH 04/15] feat: turn on too many requests for a period of time (#647) --- atoma-bin/atoma_node.rs | 2 ++ atoma-service/src/config.rs | 3 +++ atoma-service/src/handlers/chat_completions.rs | 6 ++++++ atoma-service/src/handlers/completions.rs | 6 ++++++ atoma-service/src/middleware.rs | 9 +++++++++ atoma-service/src/server.rs | 8 +++++++- atoma-service/src/tests.rs | 2 ++ config.example.toml | 1 + 8 files changed, 36 insertions(+), 1 deletion(-) diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index 6359881d..1f565724 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -371,6 +371,8 @@ async fn main() -> Result<()> { keystore: Arc::new(keystore), address_index, whitelist_sui_addresses_for_fiat: config.service.whitelist_sui_addresses_for_fiat, + too_many_requests: Arc::new(DashMap::new()), + too_many_requests_timeout_ms: u128::from(config.service.too_many_requests_timeout_ms), }; let chat_completions_service_urls = app_state diff --git a/atoma-service/src/config.rs b/atoma-service/src/config.rs index f7544506..045adba2 100644 --- a/atoma-service/src/config.rs +++ b/atoma-service/src/config.rs @@ -57,6 +57,9 @@ pub struct AtomaServiceConfig { /// List of allowed sui addresses for fiat payments. pub whitelist_sui_addresses_for_fiat: Vec, + + /// The timeout for the too many requests error in milliseconds. + pub too_many_requests_timeout_ms: u64, } impl AtomaServiceConfig { diff --git a/atoma-service/src/handlers/chat_completions.rs b/atoma-service/src/handlers/chat_completions.rs index 89ec218f..b2d4dbbb 100644 --- a/atoma-service/src/handlers/chat_completions.rs +++ b/atoma-service/src/handlers/chat_completions.rs @@ -908,6 +908,9 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.clone(), @@ -1329,6 +1332,9 @@ pub mod utils { endpoint: endpoint.to_string(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.to_string(), diff --git a/atoma-service/src/handlers/completions.rs b/atoma-service/src/handlers/completions.rs index e3bbc3b5..8b3375f7 100644 --- a/atoma-service/src/handlers/completions.rs +++ b/atoma-service/src/handlers/completions.rs @@ -882,6 +882,9 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.clone(), @@ -1292,6 +1295,9 @@ pub mod utils { endpoint: endpoint.to_string(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.to_string(), diff --git a/atoma-service/src/middleware.rs b/atoma-service/src/middleware.rs index 8371a089..620ffc12 100644 --- a/atoma-service/src/middleware.rs +++ b/atoma-service/src/middleware.rs @@ -811,6 +811,15 @@ pub async fn verify_permissions( message: "Model is not a string".to_string(), endpoint: endpoint.clone(), })?; + if let Some(trigger_time) = state.too_many_requests.get(model) { + if trigger_time.elapsed().as_millis() < state.too_many_requests_timeout_ms { + return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { + message: "Too many requests".to_string(), + endpoint: endpoint.clone(), + }); + } + state.too_many_requests.remove(model); + } if !state.models.contains(&model.to_string()) { return Err(AtomaServiceError::InvalidBody { message: format!("Model not supported, supported models: {:?}", state.models), diff --git a/atoma-service/src/server.rs b/atoma-service/src/server.rs index 0db7e1bc..9575db5f 100644 --- a/atoma-service/src/server.rs +++ b/atoma-service/src/server.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Instant}; use atoma_confidential::types::{ ConfidentialComputeDecryptionRequest, ConfidentialComputeDecryptionResponse, @@ -204,6 +204,12 @@ pub struct AppState { /// The Sui address of the clients that are allowed to use fiat. pub whitelist_sui_addresses_for_fiat: Vec, + + /// When was the too many requests triggered for each model. + pub too_many_requests: Arc>, + + /// The time for which we triiger too many requests since the first occurrence. + pub too_many_requests_timeout_ms: u128, } /// Creates and configures the main router for the application. diff --git a/atoma-service/src/tests.rs b/atoma-service/src/tests.rs index c006dbb5..a9ff3732 100644 --- a/atoma-service/src/tests.rs +++ b/atoma-service/src/tests.rs @@ -341,6 +341,8 @@ mod middleware { address_index: 0, stack_retrieve_sender, whitelist_sui_addresses_for_fiat: vec![], + too_many_requests: Arc::new(DashMap::new()), + too_many_requests_timeout_ms: 0, }, public_key, signature, diff --git a/config.example.toml b/config.example.toml index da176dac..744a35e5 100644 --- a/config.example.toml +++ b/config.example.toml @@ -43,6 +43,7 @@ models = [ "Infermatic/Llama-3.3-70B-Instruct-FP8-Dyna revisions = [ "main" ] sentry_dsn = "" # Sentry DSN (for use in sentry, you need to set the Sentry DSN) service_bind_address = "0.0.0.0:3000" +too_many_requests_timeout_ms = 2000 # Timeout for too many requests flag in milliseconds whitelist_sui_addresses_for_fiat = [ ] # Sui addresses that are allowed to use fiat payments [atoma_sui] From 94d157b83ef83a5a9952cc09bda79e9ac6b5d22a Mon Sep 17 00:00:00 2001 From: Martin Stefcek <35243812+Cifko@users.noreply.github.com> Date: Fri, 30 May 2025 17:44:50 +0200 Subject: [PATCH 05/15] feat: add request running cap (#649) * feat: add request running cap * fix clippy --------- Co-authored-by: Jorge Antonio --- atoma-bin/atoma_node.rs | 14 +- atoma-service/src/config.rs | 4 +- .../src/handlers/chat_completions.rs | 33 ++++- atoma-service/src/handlers/completions.rs | 34 +++-- atoma-service/src/handlers/mod.rs | 131 ++++++++++-------- atoma-service/src/handlers/request_counter.rs | 73 ++++++++++ atoma-service/src/server.rs | 6 +- atoma-service/src/streamer.rs | 12 ++ atoma-service/src/tests.rs | 3 +- config.example.toml | 8 ++ 10 files changed, 243 insertions(+), 75 deletions(-) create mode 100644 atoma-service/src/handlers/request_counter.rs diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index 1f565724..6fb87822 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -4,7 +4,9 @@ use anyhow::{Context, Result}; use atoma_confidential::AtomaConfidentialCompute; use atoma_daemon::{telemetry, AtomaDaemonConfig, DaemonState}; use atoma_p2p::{AtomaP2pNode, AtomaP2pNodeConfig}; -use atoma_service::{config::AtomaServiceConfig, server::AppState}; +use atoma_service::{ + config::AtomaServiceConfig, handlers::request_counter::RequestCounter, server::AppState, +}; use atoma_state::{config::AtomaStateManagerConfig, AtomaState, AtomaStateManager}; use atoma_sui::{client::Client, config::Config, subscriber::Subscriber}; use atoma_utils::spawn_with_shutdown; @@ -373,6 +375,7 @@ async fn main() -> Result<()> { whitelist_sui_addresses_for_fiat: config.service.whitelist_sui_addresses_for_fiat, too_many_requests: Arc::new(DashMap::new()), too_many_requests_timeout_ms: u128::from(config.service.too_many_requests_timeout_ms), + running_num_requests: Arc::new(RequestCounter::new()), }; let chat_completions_service_urls = app_state @@ -380,7 +383,14 @@ async fn main() -> Result<()> { .iter() .flat_map(|(model, urls)| { urls.iter() - .map(|(url, job)| (model.clone(), url.clone(), job.clone())) + .map(|(url, job, max_number_of_running_requests)| { + ( + model.clone(), + url.clone(), + job.clone(), + *max_number_of_running_requests, + ) + }) }) .collect(); atoma_service::handlers::inference_service_metrics::start_metrics_updater( diff --git a/atoma-service/src/config.rs b/atoma-service/src/config.rs index 045adba2..f7cff89a 100644 --- a/atoma-service/src/config.rs +++ b/atoma-service/src/config.rs @@ -9,10 +9,10 @@ use serde::Deserialize; /// including URLs for various services and a list of models. #[derive(Debug, Deserialize)] pub struct AtomaServiceConfig { - /// URL for the chat completions service. + /// URL for the chat completions service with maximum concurrency settings. /// /// This field specifies the endpoint for the chat completions service used by the Atoma Service. - pub chat_completions_service_urls: HashMap>, + pub chat_completions_service_urls: HashMap>, /// URL for the embeddings service. /// diff --git a/atoma-service/src/handlers/chat_completions.rs b/atoma-service/src/handlers/chat_completions.rs index b2d4dbbb..56f4d785 100644 --- a/atoma-service/src/handlers/chat_completions.rs +++ b/atoma-service/src/handlers/chat_completions.rs @@ -49,7 +49,10 @@ use tracing::{debug, info, instrument}; use utoipa::OpenApi; use serde::Deserialize; -use std::time::{Duration, Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use crate::{ error::AtomaServiceError, @@ -901,12 +904,16 @@ async fn handle_streaming_response( } })?; let (chat_completions_service_url, status_code) = - get_best_available_chat_completions_service_url(chat_completions_service_urls, model) - .await - .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { - message: e.to_string(), - endpoint: endpoint.clone(), - })?; + get_best_available_chat_completions_service_url( + &state.running_num_requests, + chat_completions_service_urls, + &model.to_lowercase(), + ) + .await + .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { + message: e.to_string(), + endpoint: endpoint.clone(), + })?; if status_code == StatusCode::TOO_MANY_REQUESTS { state .too_many_requests @@ -927,6 +934,9 @@ async fn handle_streaming_response( .send() .await .map_err(|e| { + state + .running_num_requests + .decrement(&chat_completions_service_url); AtomaServiceError::InternalError { message: format!( "Error sending request to inference service, for request with payload hash: {:?}, and stack small id: {:?}, with error: {}", @@ -939,6 +949,9 @@ async fn handle_streaming_response( })?; if !response.status().is_success() { + state + .running_num_requests + .decrement(&chat_completions_service_url); let status = response.status(); let bytes = response .bytes() @@ -992,6 +1005,8 @@ async fn handle_streaming_response( price_per_one_million_compute_units, user_id, user_address, + Arc::clone(&state.running_num_requests), + chat_completions_service_url, )) .keep_alive( axum::response::sse::KeepAlive::new() @@ -1323,6 +1338,7 @@ pub mod utils { })?; let (chat_completions_service_url, status_code) = get_best_available_chat_completions_service_url( + &state.running_num_requests, chat_completions_service_url_services, model, ) @@ -1348,6 +1364,9 @@ pub mod utils { .json(&payload) .send() .await; + state + .running_num_requests + .decrement(&chat_completions_service_url); let response = response.map_err(|e| { AtomaServiceError::InternalError { message: format!( diff --git a/atoma-service/src/handlers/completions.rs b/atoma-service/src/handlers/completions.rs index 8b3375f7..a2da3069 100644 --- a/atoma-service/src/handlers/completions.rs +++ b/atoma-service/src/handlers/completions.rs @@ -37,7 +37,10 @@ use tracing::{debug, info, instrument}; use utoipa::OpenApi; use serde::Deserialize; -use std::time::{Duration, Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use crate::{ error::AtomaServiceError, @@ -874,13 +877,16 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), } })?; - let (completions_service_url, status_code) = - get_best_available_chat_completions_service_url(chat_completions_service_urls, model) - .await - .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { - message: e.to_string(), - endpoint: endpoint.clone(), - })?; + let (completions_service_url, status_code) = get_best_available_chat_completions_service_url( + &state.running_num_requests, + chat_completions_service_urls, + model, + ) + .await + .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { + message: e.to_string(), + endpoint: endpoint.clone(), + })?; if status_code == StatusCode::TOO_MANY_REQUESTS { state .too_many_requests @@ -898,6 +904,9 @@ async fn handle_streaming_response( .send() .await .map_err(|e| { + state + .running_num_requests + .decrement(&completions_service_url); AtomaServiceError::InternalError { message: format!( "Error sending request to inference service, for request with payload hash: {:?}, and stack small id: {:?}, with error: {}", @@ -910,6 +919,9 @@ async fn handle_streaming_response( })?; if !response.status().is_success() { + state + .running_num_requests + .decrement(&completions_service_url); let status = response.status(); let bytes = response .bytes() @@ -962,6 +974,8 @@ async fn handle_streaming_response( price_per_one_million_tokens, user_id, user_address, + Arc::clone(&state.running_num_requests), + completions_service_url, )) .keep_alive( axum::response::sse::KeepAlive::new() @@ -1286,6 +1300,7 @@ pub mod utils { })?; let (completions_service_url, status_code) = get_best_available_chat_completions_service_url( + &state.running_num_requests, completions_service_url_services, model, ) @@ -1308,6 +1323,9 @@ pub mod utils { .json(&payload) .send() .await; + state + .running_num_requests + .decrement(&completions_service_url); let response = response .map_err(|e| { AtomaServiceError::InternalError { diff --git a/atoma-service/src/handlers/mod.rs b/atoma-service/src/handlers/mod.rs index aa79a7e4..201d2213 100644 --- a/atoma-service/src/handlers/mod.rs +++ b/atoma-service/src/handlers/mod.rs @@ -4,6 +4,7 @@ pub mod completions; pub mod embeddings; pub mod image_generations; pub mod metrics; +pub mod request_counter; pub mod request_model; pub mod stop_streamer; @@ -572,7 +573,6 @@ pub fn handle_status_code_error( pub mod inference_service_metrics { use futures::future::join_all; - use opentelemetry::KeyValue; use prometheus_parse::Scrape; use prometheus_parse::Value; use rand::Rng; @@ -581,12 +581,13 @@ pub mod inference_service_metrics { use std::time::Duration; use tokio::sync::RwLock; use tokio::time; + use tracing::info; - use crate::handlers::metrics::CHAT_COMPLETIONS_TOO_MANY_REQUESTS; + use crate::handlers::InferenceService; use hyper::StatusCode; - use tracing::{info, instrument}; + use tracing::instrument; - use super::InferenceService; + use super::request_counter::RequestCounter; pub type Result = std::result::Result; type MetricValue = ChatCompletionsMetrics; @@ -620,6 +621,8 @@ pub mod inference_service_metrics { num_queued_requests: f64, /// The number of running requests num_running_requests: f64, + /// The maximum number of running requests allowed for this url. + max_number_of_running_requests: usize, } /// Cache structure to store metrics @@ -660,10 +663,10 @@ pub mod inference_service_metrics { /// * `metrics_update_interval` - The interval in seconds to update the metrics. #[instrument(level = "info", skip_all)] pub fn start_metrics_updater( - chat_completions_service_urls: Vec<(String, String, String)>, + chat_completions_service_urls: Vec<(String, String, String, usize)>, metrics_update_interval: Option, ) { - type ChatCompletionsServiceUrls = Vec<(String, String, String)>; + type ChatCompletionsServiceUrls = Vec<(String, String, String, usize)>; info!( target = "atoma-service", module = "inference_service_metrics", @@ -676,7 +679,7 @@ pub mod inference_service_metrics { ) = chat_completions_service_urls .iter() .cloned() - .partition(|(_, _, job)| job.contains("vllm")); + .partition(|(_, _, job, _)| job.contains("vllm")); info!( target = "atoma-service", module = "inference_service_metrics", @@ -756,12 +759,12 @@ pub mod inference_service_metrics { /// gracefully. async fn get_metrics( inference_service: &InferenceService, - jobs_with_url: &[(String, String, String)], // (model, url, job) + jobs_with_url: &[(String, String, String, usize)], // (model, url, job, max_concurrent_requests) ) -> Vec> { let tasks = jobs_with_url .iter() - .map(|(model, chat_completions_service_url, job)| async move { + .map(|(model, chat_completions_service_url, job, max_number_of_running_requests)| async move { let response = HTTP_CLIENT .get(format!("{chat_completions_service_url}/metrics")) .send() @@ -790,6 +793,7 @@ pub mod inference_service_metrics { chat_completions_service_url: chat_completions_service_url.clone(), num_queued_requests, num_running_requests, + max_number_of_running_requests:*max_number_of_running_requests }) }); join_all(tasks).await @@ -895,14 +899,18 @@ pub mod inference_service_metrics { #[instrument(level = "info", skip_all, fields(model=model))] #[allow(clippy::float_cmp)] pub async fn get_best_available_chat_completions_service_url( - chat_completions_service_urls: &[(String, String)], + running_num_requests: &RequestCounter, + chat_completions_service_urls: &[(String, String, usize)], // (url, job, max_concurrent_requests) model: &str, ) -> Result<(String, StatusCode)> { - const MAX_ALLOWED_NUM_QUEUED_REQUESTS: f64 = 1.0; // Default to 1 request - - type ChatCompletionsServiceUrls = Vec<(String, String)>; + type ChatCompletionsServiceUrls = Vec<(String, String, usize)>; if chat_completions_service_urls.is_empty() { + tracing::warn!( + target = "atoma-service", + model = model, + "No chat completions service URLs provided for model." + ); return Err( ChatCompletionsMetricsError::NoChatCompletionsServiceUrlsFound(model.to_string()), ); @@ -919,7 +927,7 @@ pub mod inference_service_metrics { ) = chat_completions_service_urls .iter() .cloned() - .partition(|(_, job)| job.contains("vllm")); + .partition(|(_, job, _)| job.contains("vllm")); tracing::debug!( target = "atoma-service", @@ -940,11 +948,22 @@ pub mod inference_service_metrics { level = "info", "No cached vLLM metrics, getting them directly" ); - let vllm_chat_completions_service_urls_with_model: Vec<(String, String, String)> = - vllm_chat_completions_service_urls - .iter() - .map(|(url, job)| (model.to_string(), url.clone(), job.clone())) - .collect(); + let vllm_chat_completions_service_urls_with_model: Vec<( + String, + String, + String, + usize, + )> = vllm_chat_completions_service_urls + .iter() + .map(|(url, job, max_concurrent_requests)| { + ( + model.to_string(), + url.clone(), + job.clone(), + *max_concurrent_requests, + ) + }) + .collect(); get_metrics( &InferenceService::Vllm, &vllm_chat_completions_service_urls_with_model, @@ -962,11 +981,22 @@ pub mod inference_service_metrics { level = "info", "No cached SgLang metrics, getting them directly" ); - let sglang_chat_completions_service_urls_with_model: Vec<(String, String, String)> = - sglang_chat_completions_service_urls - .iter() - .map(|(url, job)| (model.to_string(), url.clone(), job.clone())) - .collect(); + let sglang_chat_completions_service_urls_with_model: Vec<( + String, + String, + String, + usize, + )> = sglang_chat_completions_service_urls + .iter() + .map(|(url, job, max_concurrent_requests)| { + ( + model.to_string(), + url.clone(), + job.clone(), + *max_concurrent_requests, + ) + }) + .collect(); get_metrics( &InferenceService::SgLang, &sglang_chat_completions_service_urls_with_model, @@ -989,6 +1019,7 @@ pub mod inference_service_metrics { chat_completions_service_url, num_queued_requests, num_running_requests, + max_number_of_running_requests, }) => { tracing::info!( target = "atoma-service", @@ -1014,6 +1045,7 @@ pub mod inference_service_metrics { chat_completions_service_url, num_queued_requests, num_running_requests, + max_number_of_running_requests, }); } Err(e) => { @@ -1040,39 +1072,30 @@ pub mod inference_service_metrics { } // Select the best available chat completions service URL based on the number of queued and running requests. - let best_metrics = metrics_results - .iter() - .min_by_key(|metric| { - ( - metric.num_queued_requests as i64, - metric.num_running_requests as i64, - ) - }) - .unwrap(); + metrics_results.sort_by_key(|metric| { + ( + metric.num_queued_requests as i64, + metric.num_running_requests as i64, + ) + }); - if best_metrics.num_queued_requests >= MAX_ALLOWED_NUM_QUEUED_REQUESTS { - tracing::warn!( - target = "atoma-service", - level = "warn", - "Node is currently under high load, the best available chat completions service URL for model: {model} has a num queue requests of at least {} requests", - best_metrics.num_queued_requests - ); - CHAT_COMPLETIONS_TOO_MANY_REQUESTS.add(1, &[KeyValue::new("model", model.to_string())]); - return Ok(( - chat_completions_service_urls[0].0.clone(), - StatusCode::TOO_MANY_REQUESTS, - )); + for metric in metrics_results { + if running_num_requests.increment( + &metric.chat_completions_service_url, + metric.max_number_of_running_requests, + ) { + let best_url = metric.chat_completions_service_url.clone(); + tracing::info!( + target = "atoma-service", + level = "info", + "Best available chat completions service URL for model: {model} is: {best_url} with and {} queue requests", + metric.num_queued_requests + ); + return Ok((best_url, StatusCode::OK)); + } } - let best_url = best_metrics.chat_completions_service_url.clone(); - tracing::info!( - target = "atoma-service", - level = "info", - "Best available chat completions service URL for model: {model} is: {best_url} with and {} queue requests", - best_metrics.num_queued_requests - ); - - Ok((best_url, StatusCode::OK)) + return Ok((String::new(), StatusCode::TOO_MANY_REQUESTS)); } #[derive(Debug, thiserror::Error, Clone)] diff --git a/atoma-service/src/handlers/request_counter.rs b/atoma-service/src/handlers/request_counter.rs new file mode 100644 index 00000000..73444fca --- /dev/null +++ b/atoma-service/src/handlers/request_counter.rs @@ -0,0 +1,73 @@ +use atoma_p2p::metrics::RUNNING_REQUESTS; +use dashmap::{DashMap, Entry}; +use opentelemetry::KeyValue; +use tracing::error; + +/// A thread-safe request counter that tracks the number of requests being processed for each inference service. +#[derive(Clone, Debug)] +pub struct RequestCounter { + /// A map that holds the count of running requests for each inference service. + running_num_requests: DashMap, +} + +impl Default for RequestCounter { + fn default() -> Self { + Self::new() + } +} + +impl RequestCounter { + /// Creates a new instance of `RequestCounter`. + #[must_use] + pub fn new() -> Self { + Self { + running_num_requests: DashMap::new(), + } + } + + /// Increments the count for the given key or initializes it to 1 if it does not exist. + pub fn increment(&self, key: &str, max_value: usize) -> bool { + let mut entry = self + .running_num_requests + .entry(key.to_string()) + .or_insert(0); + if *entry >= max_value { + false + } else { + *entry += 1; + RUNNING_REQUESTS.record(*entry as u64, &[KeyValue::new("service", key.to_string())]); + true + } + } + + /// Decrements the count for the given key. If the count reaches zero, the entry is removed. + pub fn decrement(&self, key: &str) { + match self.running_num_requests.entry(key.to_string()) { + Entry::Occupied(mut entry) => { + let count = entry.get_mut(); + *count -= 1; + RUNNING_REQUESTS + .record(*count as u64, &[KeyValue::new("service", key.to_string())]); + if *count == 0 { + entry.remove(); + } + } + Entry::Vacant(_) => { + // This should not happen + error!( + target = "atoma-service", + level = "info", + event = "chat-completions-handler", + "Attempted to decrement a non-existent key: {}", + key + ); + } + } + } + + /// Retrieves the current count for the given key. + #[must_use] + pub fn get_count(&self, key: &str) -> usize { + self.running_num_requests.get(key).map_or(0, |entry| *entry) + } +} diff --git a/atoma-service/src/server.rs b/atoma-service/src/server.rs index 9575db5f..125a9d9e 100644 --- a/atoma-service/src/server.rs +++ b/atoma-service/src/server.rs @@ -54,6 +54,7 @@ use crate::{ confidential_image_generations_handler, image_generations_handler, CONFIDENTIAL_IMAGE_GENERATIONS_PATH, IMAGE_GENERATIONS_PATH, }, + request_counter::RequestCounter, stop_streamer::stop_streamer_handler, }, middleware::{ @@ -174,7 +175,7 @@ pub struct AppState { /// These URLs point to the external services responsible for performing /// AI model chat completions. The application forwards requests to this /// service to obtain AI-generated responses. - pub chat_completions_service_urls: HashMap>, + pub chat_completions_service_urls: HashMap>, /// URL for the embeddings service. /// @@ -210,6 +211,9 @@ pub struct AppState { /// The time for which we triiger too many requests since the first occurrence. pub too_many_requests_timeout_ms: u128, + + /// Number of running requests for each inference service. + pub running_num_requests: Arc, } /// Creates and configures the main router for the application. diff --git a/atoma-service/src/streamer.rs b/atoma-service/src/streamer.rs index bb1ffc2b..9ded19e1 100644 --- a/atoma-service/src/streamer.rs +++ b/atoma-service/src/streamer.rs @@ -31,6 +31,7 @@ use crate::{ CHAT_COMPLETIONS_STREAMING_LATENCY_METRICS, CHAT_COMPLETIONS_TIME_TO_FIRST_TOKEN, TOTAL_COMPLETED_REQUESTS, }, + request_counter::RequestCounter, update_fiat_amount, update_stack_num_compute_units, USAGE_KEY, }, server::utils, @@ -143,6 +144,10 @@ pub struct Streamer { user_id: Option, /// The user address for the request user_address: String, + /// A map to keep track of the number of requests currently being processed + running_num_requests: Arc, + /// The URL of the chat completions service + chat_completions_service_url: String, } /// Represents the various states of a streaming process @@ -180,6 +185,8 @@ impl Streamer { price_per_one_million_tokens: i64, user_id: Option, user_address: String, + running_num_requests: Arc, + chat_completions_service_url: String, ) -> Self { Self { concurrent_requests, @@ -206,6 +213,8 @@ impl Streamer { price_per_one_million_tokens, user_id, user_address, + running_num_requests, + chat_completions_service_url, } } @@ -905,6 +914,9 @@ impl Drop for Streamer { ) )] fn drop(&mut self) { + self.running_num_requests + .decrement(&self.chat_completions_service_url); + if self.is_final_chunk_handled || matches!(self.status, StreamStatus::Failed(_)) { TOTAL_COMPLETED_REQUESTS.add(1, &[KeyValue::new(MODEL_KEY, self.model.clone())]); return; diff --git a/atoma-service/src/tests.rs b/atoma-service/src/tests.rs index a9ff3732..9410bfca 100644 --- a/atoma-service/src/tests.rs +++ b/atoma-service/src/tests.rs @@ -34,7 +34,7 @@ mod middleware { use crate::{ handlers::{ chat_completions::CHAT_COMPLETIONS_PATH, embeddings::EMBEDDINGS_PATH, - image_generations::IMAGE_GENERATIONS_PATH, + image_generations::IMAGE_GENERATIONS_PATH, request_counter::RequestCounter, }, middleware::{ confidential_compute_middleware, signature_verification_middleware, verify_permissions, @@ -343,6 +343,7 @@ mod middleware { whitelist_sui_addresses_for_fiat: vec![], too_many_requests: Arc::new(DashMap::new()), too_many_requests_timeout_ms: 0, + running_num_requests: Arc::new(RequestCounter::new()), }, public_key, signature, diff --git a/config.example.toml b/config.example.toml index 744a35e5..3aa7c708 100644 --- a/config.example.toml +++ b/config.example.toml @@ -3,34 +3,42 @@ chat_completions_service_urls = { "Infermatic/Llama-3.3-70B-Instruct-FP8-Dynamic [ "http://chat-completions1:8000", "vllm1", + 256, ], [ "http://chat-completions2:8000", "vllm2", + 256, ], [ "http://chat-completions3:8000", "vllm3", + 256, ], [ "http://chat-completions4:8000", "vllm4", + 256, ], [ "http://chat-completions5:8000", "vllm5", + 256, ], [ "http://chat-completions6:8000", "vllm6", + 256, ], [ "http://chat-completions7:8000", "vllm7", + 256, ], [ "http://chat-completions8:8000", "vllm8", + 256, ], ] } embeddings_service_url = "http://embeddings:80" From 1bbb4235e0db0897e3836b6772e777203f11ae63 Mon Sep 17 00:00:00 2001 From: Jorge Antonio Date: Fri, 30 May 2025 17:38:00 +0100 Subject: [PATCH 06/15] refactor num running requests for prometheus check --- atoma-service/src/handlers/metrics.rs | 17 ++++++++++++++++- atoma-service/src/handlers/mod.rs | 2 +- atoma-service/src/handlers/request_counter.rs | 8 +++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/atoma-service/src/handlers/metrics.rs b/atoma-service/src/handlers/metrics.rs index 7515e0eb..f10a1f5d 100644 --- a/atoma-service/src/handlers/metrics.rs +++ b/atoma-service/src/handlers/metrics.rs @@ -1,6 +1,6 @@ use opentelemetry::{ global, - metrics::{Counter, Histogram, Meter, UpDownCounter}, + metrics::{Counter, Gauge, Histogram, Meter, UpDownCounter}, }; use std::sync::LazyLock; @@ -616,3 +616,18 @@ pub static SIGNATURE_VERIFICATION_MIDDLEWARE_SUCCESSFUL_TIME: LazyLock> = LazyLock::new(|| { + GLOBAL_METER + .u64_gauge("atoma_num_running_requests") + .with_description("Number of running requests") + .with_unit("requests") + .build() +}); diff --git a/atoma-service/src/handlers/mod.rs b/atoma-service/src/handlers/mod.rs index 201d2213..7482dd53 100644 --- a/atoma-service/src/handlers/mod.rs +++ b/atoma-service/src/handlers/mod.rs @@ -793,7 +793,7 @@ pub mod inference_service_metrics { chat_completions_service_url: chat_completions_service_url.clone(), num_queued_requests, num_running_requests, - max_number_of_running_requests:*max_number_of_running_requests + max_number_of_running_requests: *max_number_of_running_requests, }) }); join_all(tasks).await diff --git a/atoma-service/src/handlers/request_counter.rs b/atoma-service/src/handlers/request_counter.rs index 73444fca..a0570e85 100644 --- a/atoma-service/src/handlers/request_counter.rs +++ b/atoma-service/src/handlers/request_counter.rs @@ -1,8 +1,9 @@ -use atoma_p2p::metrics::RUNNING_REQUESTS; use dashmap::{DashMap, Entry}; use opentelemetry::KeyValue; use tracing::error; +use super::metrics::NUM_RUNNING_REQUESTS; + /// A thread-safe request counter that tracks the number of requests being processed for each inference service. #[derive(Clone, Debug)] pub struct RequestCounter { @@ -35,7 +36,8 @@ impl RequestCounter { false } else { *entry += 1; - RUNNING_REQUESTS.record(*entry as u64, &[KeyValue::new("service", key.to_string())]); + NUM_RUNNING_REQUESTS + .record(*entry as u64, &[KeyValue::new("service", key.to_string())]); true } } @@ -46,7 +48,7 @@ impl RequestCounter { Entry::Occupied(mut entry) => { let count = entry.get_mut(); *count -= 1; - RUNNING_REQUESTS + NUM_RUNNING_REQUESTS .record(*count as u64, &[KeyValue::new("service", key.to_string())]); if *count == 0 { entry.remove(); From 4e9f9697c9a2a97b52af37418abd091dc82d0950 Mon Sep 17 00:00:00 2001 From: Jorge Antonio Date: Fri, 30 May 2025 17:51:40 +0100 Subject: [PATCH 07/15] logs --- atoma-service/src/middleware.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/atoma-service/src/middleware.rs b/atoma-service/src/middleware.rs index 620ffc12..bdc32318 100644 --- a/atoma-service/src/middleware.rs +++ b/atoma-service/src/middleware.rs @@ -813,6 +813,13 @@ pub async fn verify_permissions( })?; if let Some(trigger_time) = state.too_many_requests.get(model) { if trigger_time.elapsed().as_millis() < state.too_many_requests_timeout_ms { + tracing::info!( + target = "atoma-service", + level = "info", + "Too many requests for model: {model}, endpoint: {endpoint}, elapsed trigger time: {} and timeout: {}", + trigger_time.elapsed().as_millis(), + state.too_many_requests_timeout_ms + ); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.clone(), From 03884a49c074500ea4106cc4fff38294f25be935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Ant=C3=B3nio?= Date: Mon, 2 Jun 2025 09:06:52 +0100 Subject: [PATCH 08/15] handle deadlock for too many requests timeout trigger check (#650) --- atoma-service/src/middleware.rs | 69 +++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/atoma-service/src/middleware.rs b/atoma-service/src/middleware.rs index bdc32318..04fd20fc 100644 --- a/atoma-service/src/middleware.rs +++ b/atoma-service/src/middleware.rs @@ -811,22 +811,7 @@ pub async fn verify_permissions( message: "Model is not a string".to_string(), endpoint: endpoint.clone(), })?; - if let Some(trigger_time) = state.too_many_requests.get(model) { - if trigger_time.elapsed().as_millis() < state.too_many_requests_timeout_ms { - tracing::info!( - target = "atoma-service", - level = "info", - "Too many requests for model: {model}, endpoint: {endpoint}, elapsed trigger time: {} and timeout: {}", - trigger_time.elapsed().as_millis(), - state.too_many_requests_timeout_ms - ); - return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { - message: "Too many requests".to_string(), - endpoint: endpoint.clone(), - }); - } - state.too_many_requests.remove(model); - } + utils::check_if_too_many_requests(&state, model, &endpoint)?; if !state.models.contains(&model.to_string()) { return Err(AtomaServiceError::InvalidBody { message: format!("Model not supported, supported models: {:?}", state.models), @@ -1603,4 +1588,56 @@ pub mod utils { } Ok(()) } + + /// Checks if the model has too many requests. + /// + /// This function checks if the model has too many requests by checking if the elapsed time since the first occurrence is less than the timeout. + /// + /// # Arguments + /// * `state` - The application state containing the too many requests map + /// * `model` - The model to check + /// * `endpoint` - The API endpoint path being accessed (used for error context) + /// + /// # Returns + /// * `Ok(())` - If the model has too many requests + /// * `Err(AtomaServiceError)` - If the model has too many requests + /// + /// # Errors + /// This function will return an error if: + /// - The model has too many requests + /// - The elapsed time since the first occurrence is less than the timeout + #[instrument(level = "info", skip_all, err)] + pub fn check_if_too_many_requests( + state: &AppState, + model: &str, + endpoint: &str, + ) -> Result<(), AtomaServiceError> { + match state.too_many_requests.entry(model.to_string()) { + dashmap::mapref::entry::Entry::Occupied(occupied_entry) => { + let elapsed_ms = occupied_entry.get().elapsed().as_millis(); + + if elapsed_ms < state.too_many_requests_timeout_ms { + tracing::info!( + target = "atoma-service", + level = "info", + "Too many requests for model: {model}, endpoint: {endpoint}, elapsed trigger time: {elapsed_ms} and timeout: {}", + state.too_many_requests_timeout_ms + ); + return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { + message: "Too many requests".to_string(), + endpoint: endpoint.to_string(), + }); + } + occupied_entry.remove(); + } + dashmap::mapref::entry::Entry::Vacant(_) => { + tracing::debug!( + target = "atoma-service", + level = "debug", + "Model is not in the `too_many_requests` map, so no action is needed here. Processing can continue." + ); + } + } + Ok(()) + } } From 6b669a785f0682cc285777a3502d7b85e9d1bdb7 Mon Sep 17 00:00:00 2001 From: Martin Stefcek <35243812+Cifko@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:38:23 +0200 Subject: [PATCH 09/15] feat: add mem usage (#651) * feat: add memusage to get_metrics * add lower threshold for disabling the flag * fix clippy * address 2 comments * add values to config * fix * fix tests * fix name --- atoma-bin/atoma_node.rs | 4 +- atoma-service/src/config.rs | 5 + .../src/handlers/chat_completions.rs | 10 +- atoma-service/src/handlers/completions.rs | 10 +- atoma-service/src/handlers/mod.rs | 225 +++++++++++++----- atoma-service/src/middleware.rs | 55 +++-- atoma-service/src/server.rs | 12 +- atoma-service/src/tests.rs | 4 +- config.example.toml | 2 + 9 files changed, 229 insertions(+), 98 deletions(-) diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index 6fb87822..16f87281 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -373,9 +373,11 @@ async fn main() -> Result<()> { keystore: Arc::new(keystore), address_index, whitelist_sui_addresses_for_fiat: config.service.whitelist_sui_addresses_for_fiat, - too_many_requests: Arc::new(DashMap::new()), + too_many_requests: Arc::new(DashSet::new()), too_many_requests_timeout_ms: u128::from(config.service.too_many_requests_timeout_ms), running_num_requests: Arc::new(RequestCounter::new()), + memory_lower_threshold: config.service.memory_lower_threshold, + memory_upper_threshold: config.service.memory_upper_threshold, }; let chat_completions_service_urls = app_state diff --git a/atoma-service/src/config.rs b/atoma-service/src/config.rs index f7cff89a..e6725ff3 100644 --- a/atoma-service/src/config.rs +++ b/atoma-service/src/config.rs @@ -60,6 +60,11 @@ pub struct AtomaServiceConfig { /// The timeout for the too many requests error in milliseconds. pub too_many_requests_timeout_ms: u64, + + ///Lower threshold for memory usage, if the memory usage goes below this value, the service will not be considered overloaded + pub memory_lower_threshold: f64, + /// Upper threshold for memory usage, if the memory usage goes above this value, the service will be considered overloaded + pub memory_upper_threshold: f64, } impl AtomaServiceConfig { diff --git a/atoma-service/src/handlers/chat_completions.rs b/atoma-service/src/handlers/chat_completions.rs index 56f4d785..20fa6ae9 100644 --- a/atoma-service/src/handlers/chat_completions.rs +++ b/atoma-service/src/handlers/chat_completions.rs @@ -908,6 +908,7 @@ async fn handle_streaming_response( &state.running_num_requests, chat_completions_service_urls, &model.to_lowercase(), + state.memory_upper_threshold, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -915,9 +916,7 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state - .too_many_requests - .insert(model.to_string(), Instant::now()); + state.too_many_requests.insert(model.to_string()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.clone(), @@ -1341,6 +1340,7 @@ pub mod utils { &state.running_num_requests, chat_completions_service_url_services, model, + state.memory_upper_threshold, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -1348,9 +1348,7 @@ pub mod utils { endpoint: endpoint.to_string(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state - .too_many_requests - .insert(model.to_string(), Instant::now()); + state.too_many_requests.insert(model.to_string()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.to_string(), diff --git a/atoma-service/src/handlers/completions.rs b/atoma-service/src/handlers/completions.rs index a2da3069..0d80a52f 100644 --- a/atoma-service/src/handlers/completions.rs +++ b/atoma-service/src/handlers/completions.rs @@ -881,6 +881,7 @@ async fn handle_streaming_response( &state.running_num_requests, chat_completions_service_urls, model, + state.memory_upper_threshold, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -888,9 +889,7 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state - .too_many_requests - .insert(model.to_string(), Instant::now()); + state.too_many_requests.insert(model.to_string()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.clone(), @@ -1303,6 +1302,7 @@ pub mod utils { &state.running_num_requests, completions_service_url_services, model, + state.memory_upper_threshold, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -1310,9 +1310,7 @@ pub mod utils { endpoint: endpoint.to_string(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state - .too_many_requests - .insert(model.to_string(), Instant::now()); + state.too_many_requests.insert(model.to_string()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.to_string(), diff --git a/atoma-service/src/handlers/mod.rs b/atoma-service/src/handlers/mod.rs index 7482dd53..085163dd 100644 --- a/atoma-service/src/handlers/mod.rs +++ b/atoma-service/src/handlers/mod.rs @@ -53,9 +53,11 @@ pub const COMPLETION_TOKENS_KEY: &str = "completion_tokens"; const VLLM_RUNNING_REQUESTS_QUERY: &str = "num_requests_running"; const VLLM_QUEUED_REQUESTS_QUERY: &str = "num_requests_waiting"; +const VLLM_MEMORY_USAGE_QUERY: &str = "gpu_cache_usage_perc"; const VLLM_SERVICE_PREFIX: &str = "vllm:"; const SGLANG_RUNNING_REQUESTS_QUERY: &str = "num_running_reqs"; const SGLANG_QUEUED_REQUESTS_QUERY: &str = "num_queue_reqs"; +const SGLANG_MEMORY_USAGE_QUERY: &str = "token_usage"; const SGLANG_SERVICE_PREFIX: &str = "sglang:"; #[derive(Debug, Clone)] @@ -81,6 +83,14 @@ impl InferenceService { } } + #[must_use] + pub const fn get_usage(&self) -> &'static str { + match self { + Self::Vllm => VLLM_MEMORY_USAGE_QUERY, + Self::SgLang => SGLANG_MEMORY_USAGE_QUERY, + } + } + #[must_use] pub const fn get_service_prefix(&self) -> &'static str { match self { @@ -611,8 +621,8 @@ pub mod inference_service_metrics { }); /// Chat completions metrics - #[derive(Debug, Clone)] - struct ChatCompletionsMetrics { + #[derive(Debug, Clone, PartialEq)] + pub struct ChatCompletionsMetrics { /// The model name model: String, /// The chat completions service url @@ -621,10 +631,47 @@ pub mod inference_service_metrics { num_queued_requests: f64, /// The number of running requests num_running_requests: f64, + /// The memory usage in fraction, e.g. 1.00 means 100% memory usage + memory_usage: f64, /// The maximum number of running requests allowed for this url. max_number_of_running_requests: usize, } + impl Eq for ChatCompletionsMetrics {} + + impl PartialOrd for ChatCompletionsMetrics { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for ChatCompletionsMetrics { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.num_queued_requests + .total_cmp(&other.num_queued_requests) + .then_with(|| { + self.memory_usage + .total_cmp(&other.memory_usage) + .then_with(|| { + self.num_running_requests + .total_cmp(&other.num_running_requests) + }) + }) + } + } + + impl ChatCompletionsMetrics { + #[must_use] + pub fn above_upper_threshold_exceeded(&self, threshold: f64) -> bool { + self.memory_usage > threshold + } + + #[must_use] + pub fn under_lower_threshold(&self, threshold: f64) -> bool { + self.memory_usage <= threshold + } + } + /// Cache structure to store metrics #[derive(Debug, Default)] struct MetricsCache { @@ -787,12 +834,18 @@ pub mod inference_service_metrics { inference_service.get_running_requests_metric_name(), job, )?; + let memory_usage = extract_metric( + &metrics, + inference_service.get_usage(), + job, + )?; Ok(ChatCompletionsMetrics { model: model.clone(), chat_completions_service_url: chat_completions_service_url.clone(), num_queued_requests, num_running_requests, + memory_usage, max_number_of_running_requests: *max_number_of_running_requests, }) }); @@ -838,71 +891,37 @@ pub mod inference_service_metrics { }) } - /// Selects the best available chat completions service URL for a given model based on performance metrics. - /// - /// This function aims to distribute load and ensure optimal response times by choosing - /// the service instance that is currently performing best. The selection process prioritizes - /// services with lower requests running and queue lengths. - /// - /// # Metrics and Selection Logic: + /// Retrieves all chat completions metrics for the specified model. /// - /// 1. **Metrics Source**: Metrics for each service (vLLM or SgLang) are retrieved directly from the inference - /// service URL. - /// - /// 2. **Priority of Metrics for "Best" Service Selection**: - /// * **No Load**: If a service has zero running requests (`num_running_requests` is 0.0), - /// it's considered the best. - /// * **Number of Queued Requests**: If number of running requests are equivalent, the service - /// with the fewest `num_queue_requests` is selected. - /// - /// 3. **Handling Missing or Invalid Metrics**: - /// * If, after checking all services, no valid metrics are found for the specified `model`, - /// a service URL is chosen randomly from the initial list. - /// - /// # Load Thresholds and Behavior: - /// - /// The function defines several thresholds to manage high load scenarios: - /// * `MAX_ALLOWED_NUM_QUEUED_REQUESTS` (1.0) - /// - /// If the determined "best" service (or all services) exceeds these - /// thresholds, the function returns the first URL from the input `chat_completions_service_urls` - /// list along with a `StatusCode::TOO_MANY_REQUESTS`. The `CHAT_COMPLETIONS_TOO_MANY_REQUESTS` - /// metric counter is also incremented for the model. + /// This function fetches metrics from both vLLM and SgLang services, + /// partitions the service URLs based on the job type, and retrieves metrics + /// for each service. It returns a vector of `ChatCompletionsMetrics` for the specified model. /// /// # Arguments /// - /// * `chat_completions_service_urls`: A slice of tuples `(String, String)`, where each tuple - /// represents a service. The first `String` is the service URL, and the second `String` - /// is the job name (e.g., "vllm-service", "sglang-service"), used to determine the - /// metrics querying strategy. - /// * `model`: A string slice representing the name of the model for which the best service - /// URL is being requested. The comparison is case-insensitive. + /// * `chat_completions_service_urls` - A vector of tuples containing the chat completions service URLs, + /// job names, and maximum concurrent requests. + /// * `model` - The model name for which to retrieve metrics. /// /// # Returns /// - /// Returns a `Result<(String, StatusCode), ChatCompletionsMetricsError>`: - /// * `Ok((String, StatusCode::OK))`: On success, containing the URL of the best available - /// service and an OK status. - /// * `Ok((String, StatusCode::TOO_MANY_REQUESTS))`: If the system is determined to be under - /// high load based on the metrics thresholds. The returned `String` will be the first URL - /// from the `chat_completions_service_urls` input. - /// * `Err(ChatCompletionsMetricsError)`: If an error occurs, such as no service URLs - /// being provided or issues during metrics fetching that are not handled by fallback mechanisms. + /// Returns a `Result>` containing the metrics for the specified model. /// /// # Errors /// - /// * `ChatCompletionsMetricsError::NoChatCompletionsServiceUrlsFound`: If the input - /// `chat_completions_service_urls` slice is empty. - /// * Other variants of `ChatCompletionsMetricsError` may be returned if underlying issues - /// occur during metric collection from Prometheus (e.g., network errors, parsing errors), - /// though the function attempts to handle missing individual metrics gracefully.g - #[instrument(level = "info", skip_all, fields(model=model))] - #[allow(clippy::float_cmp)] - pub async fn get_best_available_chat_completions_service_url( - running_num_requests: &RequestCounter, + /// * `ChatCompletionsMetricsError::NoChatCompletionsServiceUrlsFound`: If no chat completions service URLs are provided. + /// * Other variants of `ChatCompletionsMetricsError` may be returned if underlying + /// issues occur during metric collection from Prometheus (e.g., network errors, + /// parsing errors), though the function attempts to handle missing individual metrics gracefully. + #[instrument( + level = "info", + skip(chat_completions_service_urls, model), + fields(model = model) + )] + pub async fn get_all_metrics( chat_completions_service_urls: &[(String, String, usize)], // (url, job, max_concurrent_requests) model: &str, - ) -> Result<(String, StatusCode)> { + ) -> Result> { type ChatCompletionsServiceUrls = Vec<(String, String, usize)>; if chat_completions_service_urls.is_empty() { @@ -1019,6 +1038,7 @@ pub mod inference_service_metrics { chat_completions_service_url, num_queued_requests, num_running_requests, + memory_usage, max_number_of_running_requests, }) => { tracing::info!( @@ -1045,6 +1065,7 @@ pub mod inference_service_metrics { chat_completions_service_url, num_queued_requests, num_running_requests, + memory_usage, max_number_of_running_requests, }); } @@ -1058,7 +1079,85 @@ pub mod inference_service_metrics { } } } + Ok(metrics_results) + } + /// Selects the best available chat completions service URL for a given model based on performance metrics. + /// + /// This function aims to distribute load and ensure optimal response times by choosing + /// the service instance that is currently performing best. The selection process prioritizes + /// services with lower requests running and queue lengths. + /// + /// # Metrics and Selection Logic: + /// + /// 1. **Metrics Source**: Metrics for each service (vLLM or SgLang) are retrieved directly from the inference + /// service URL. + /// + /// 2. **Priority of Metrics for "Best" Service Selection**: + /// * **No Load**: If a service has zero running requests (`num_running_requests` is 0.0), + /// it's considered the best. + /// * **Number of Queued Requests**: If number of running requests are equivalent, the service + /// with the fewest `num_queue_requests` is selected. + /// + /// 3. **Handling Missing or Invalid Metrics**: + /// * If, after checking all services, no valid metrics are found for the specified `model`, + /// a service URL is chosen randomly from the initial list. + /// + /// # Load Thresholds and Behavior: + /// + /// The function defines several thresholds to manage high load scenarios: + /// * `MAX_ALLOWED_NUM_QUEUED_REQUESTS` (1.0) + /// + /// If the determined "best" service (or all services) exceeds these + /// thresholds, the function returns the first URL from the input `chat_completions_service_urls` + /// list along with a `StatusCode::TOO_MANY_REQUESTS`. The `CHAT_COMPLETIONS_TOO_MANY_REQUESTS` + /// metric counter is also incremented for the model. + /// + /// # Arguments + /// + /// * `chat_completions_service_urls`: A slice of tuples `(String, String)`, where each tuple + /// represents a service. The first `String` is the service URL, and the second `String` + /// is the job name (e.g., "vllm-service", "sglang-service"), used to determine the + /// metrics querying strategy. + /// * `model`: A string slice representing the name of the model for which the best service + /// URL is being requested. The comparison is case-insensitive. + /// + /// # Returns + /// + /// Returns a `Result<(String, StatusCode), ChatCompletionsMetricsError>`: + /// * `Ok((String, StatusCode::OK))`: On success, containing the URL of the best available + /// service and an OK status. + /// * `Ok((String, StatusCode::TOO_MANY_REQUESTS))`: If the system is determined to be under + /// high load based on the metrics thresholds. The returned `String` will be the first URL + /// from the `chat_completions_service_urls` input. + /// * `Err(ChatCompletionsMetricsError)`: If an error occurs, such as no service URLs + /// being provided or issues during metrics fetching that are not handled by fallback mechanisms. + /// + /// # Errors + /// + /// * `ChatCompletionsMetricsError::NoChatCompletionsServiceUrlsFound`: If the input + /// `chat_completions_service_urls` slice is empty. + /// * Other variants of `ChatCompletionsMetricsError` may be returned if underlying issues + /// occur during metric collection from Prometheus (e.g., network errors, parsing errors), + /// though the function attempts to handle missing individual metrics gracefully.g + #[instrument(level = "info", skip_all, fields(model=model))] + #[allow(clippy::float_cmp)] + pub async fn get_best_available_chat_completions_service_url( + running_num_requests: &RequestCounter, + chat_completions_service_urls: &[(String, String, usize)], // (url, job, max_concurrent_requests) + model: &str, + memory_upper_threshold: f64, + ) -> Result<(String, StatusCode)> { + let mut metrics_results = get_all_metrics(chat_completions_service_urls, model) + .await + .map_err(|e| { + tracing::error!( + target = "atoma-service", + level = "error", + "Failed to get metrics for model: {model} with error: {e}" + ); + e + })?; if metrics_results.is_empty() { tracing::warn!( target = "atoma-service", @@ -1072,18 +1171,22 @@ pub mod inference_service_metrics { } // Select the best available chat completions service URL based on the number of queued and running requests. - metrics_results.sort_by_key(|metric| { - ( - metric.num_queued_requests as i64, - metric.num_running_requests as i64, - ) - }); + metrics_results.sort(); for metric in metrics_results { if running_num_requests.increment( &metric.chat_completions_service_url, metric.max_number_of_running_requests, ) { + if metric.above_upper_threshold_exceeded(memory_upper_threshold) { + tracing::debug!( + target = "atoma-service", + level = "debug", + "Memory usage for model: {model} is too high: {}", + metric.memory_usage + ); + continue; + } let best_url = metric.chat_completions_service_url.clone(); tracing::info!( target = "atoma-service", diff --git a/atoma-service/src/middleware.rs b/atoma-service/src/middleware.rs index 04fd20fc..701b986a 100644 --- a/atoma-service/src/middleware.rs +++ b/atoma-service/src/middleware.rs @@ -811,7 +811,7 @@ pub async fn verify_permissions( message: "Model is not a string".to_string(), endpoint: endpoint.clone(), })?; - utils::check_if_too_many_requests(&state, model, &endpoint)?; + utils::check_if_too_many_requests(&state, model, &endpoint).await?; if !state.models.contains(&model.to_string()) { return Err(AtomaServiceError::InvalidBody { message: format!("Model not supported, supported models: {:?}", state.models), @@ -1016,6 +1016,7 @@ pub mod utils { completions::RequestModelCompletions, embeddings::RequestModelEmbeddings, image_generations::RequestModelImageGenerations, + inference_service_metrics::get_all_metrics, request_model::{RequestModel, TokensEstimate}, }; @@ -1607,36 +1608,48 @@ pub mod utils { /// - The model has too many requests /// - The elapsed time since the first occurrence is less than the timeout #[instrument(level = "info", skip_all, err)] - pub fn check_if_too_many_requests( + pub async fn check_if_too_many_requests( state: &AppState, model: &str, endpoint: &str, ) -> Result<(), AtomaServiceError> { - match state.too_many_requests.entry(model.to_string()) { - dashmap::mapref::entry::Entry::Occupied(occupied_entry) => { - let elapsed_ms = occupied_entry.get().elapsed().as_millis(); - - if elapsed_ms < state.too_many_requests_timeout_ms { - tracing::info!( - target = "atoma-service", - level = "info", - "Too many requests for model: {model}, endpoint: {endpoint}, elapsed trigger time: {elapsed_ms} and timeout: {}", - state.too_many_requests_timeout_ms - ); - return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { - message: "Too many requests".to_string(), + if state.too_many_requests.get(model).is_some() { + let chat_completions_service_urls = state + .chat_completions_service_urls + .get(&model.to_lowercase()) + .ok_or_else(|| { + AtomaServiceError::InternalError { + message: format!( + "Chat completions service URL not found, likely that model is not supported by the current node: {}", + model + ), endpoint: endpoint.to_string(), - }); - } - occupied_entry.remove(); - } - dashmap::mapref::entry::Entry::Vacant(_) => { + } + })?; + let metrics = get_all_metrics(chat_completions_service_urls, model) + .await + .map_err(|e| AtomaServiceError::InternalError { + message: format!("Failed to get metrics for model {model}, with error: {e}"), + endpoint: endpoint.to_string(), + })?; + if metrics + .iter() + .any(|metric| metric.under_lower_threshold(state.memory_lower_threshold)) + { + state.too_many_requests.remove(model); tracing::debug!( target = "atoma-service", level = "debug", - "Model is not in the `too_many_requests` map, so no action is needed here. Processing can continue." + "Model {} is in the `too_many_requests` map, but metrics indicate that it is no longer exceeding the lower threshold. Removing from the map.", + model ); } + } else { + tracing::debug!( + target = "atoma-service", + level = "debug", + "Model is not in the `too_many_requests` map, so no action is needed here. Processing can continue." + ); } Ok(()) } diff --git a/atoma-service/src/server.rs b/atoma-service/src/server.rs index 125a9d9e..7e132116 100644 --- a/atoma-service/src/server.rs +++ b/atoma-service/src/server.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc, time::Instant}; +use std::{collections::HashMap, sync::Arc}; use atoma_confidential::types::{ ConfidentialComputeDecryptionRequest, ConfidentialComputeDecryptionResponse, @@ -207,13 +207,21 @@ pub struct AppState { pub whitelist_sui_addresses_for_fiat: Vec, /// When was the too many requests triggered for each model. - pub too_many_requests: Arc>, + pub too_many_requests: Arc>, /// The time for which we triiger too many requests since the first occurrence. pub too_many_requests_timeout_ms: u128, /// Number of running requests for each inference service. pub running_num_requests: Arc, + + /// The upper memory threshold for the node. + /// This threshold is used to determine when the node should start rejecting. + pub memory_upper_threshold: f64, + + /// The lower memory threshold for the node. + /// This threshold is used to determine when the node can start accepting requests again. + pub memory_lower_threshold: f64, } /// Creates and configures the main router for the application. diff --git a/atoma-service/src/tests.rs b/atoma-service/src/tests.rs index 9410bfca..b601a696 100644 --- a/atoma-service/src/tests.rs +++ b/atoma-service/src/tests.rs @@ -341,9 +341,11 @@ mod middleware { address_index: 0, stack_retrieve_sender, whitelist_sui_addresses_for_fiat: vec![], - too_many_requests: Arc::new(DashMap::new()), + too_many_requests: Arc::new(DashSet::new()), too_many_requests_timeout_ms: 0, running_num_requests: Arc::new(RequestCounter::new()), + memory_lower_threshold: 1.0, + memory_upper_threshold: 1.0, }, public_key, signature, diff --git a/config.example.toml b/config.example.toml index 3aa7c708..1a519842 100644 --- a/config.example.toml +++ b/config.example.toml @@ -46,6 +46,8 @@ image_generations_service_url = "http://image-generations:80" # List of models to be used by the service, the current value here is just a placeholder, please change it to the models you want to deploy environment = "development" # or "production" (for use in sentry, you need to set the Sentry DSN) heartbeat_url = "my-heartbeat-url" +memory_lower_threshold = 0.75 # Lower threshold for memory usage, if the memory usage goes below this value, the service will not be considered overloaded +memory_upper_threshold = 0.9 # Upper threshold for memory usage, if the memory usage goes above this value, the service will be considered overloaded metrics_update_interval = 30 models = [ "Infermatic/Llama-3.3-70B-Instruct-FP8-Dynamic" ] revisions = [ "main" ] From f34694baa83a92a6deb7f07fb90fa1344b46dc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Ant=C3=B3nio?= Date: Tue, 3 Jun 2025 17:01:49 +0100 Subject: [PATCH 10/15] feat: update sui dependencies (#654) * resolve compilation issues * ci: add caching strategy for ci * ci: optimize coverage job * ci: adjust coverage job * ci: update deny action * ci: use grcov * ci: use stable toolchain * ci: only run tests once * ci: move coverage to test file * ci: use --codecov flag & stable toolchain * ci: discard p2p tester --------- Co-authored-by: chad --- .github/workflows/coverage.yml | 26 +- .github/workflows/deny.yml | 3 +- Cargo.lock | 1191 ++++++++++++++++++++++---------- Cargo.toml | 4 +- atoma-bin/atoma_node.rs | 12 +- atoma-sui/src/client.rs | 12 +- 6 files changed, 870 insertions(+), 378 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 089ba8f3..0e8d5609 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,14 +1,7 @@ name: Coverage -on: [push, pull_request] - -env: - toolchain: stable - CARGO_HTTP_MULTIPLEXING: false - CARGO_TERM_COLOR: always - CARGO_UNSTABLE_SPARSE_REGISTRY: true - CARGO_INCREMENTAL: 0 - TERM: unknown +on: + pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -17,6 +10,9 @@ concurrency: jobs: coverage: runs-on: ubuntu-22.04 + env: + toolchain: stable + CARGO_TERM_COLOR: always services: postgres: image: postgres:13 @@ -33,6 +29,11 @@ jobs: --health-retries 5 steps: - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.toolchain }} + components: clippy - name: Install system dependencies run: | @@ -40,13 +41,14 @@ jobs: sudo apt-get install -y pkg-config libtss2-dev - name: Install cargo-llvm-cov - run: cargo install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov - name: Generate coverage - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info + run: cargo llvm-cov --all-features --workspace --exclude atoma-p2p-tester --codecov --output-path codecov.json - name: Upload to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lcov.info + files: codecov.json + fail_ci_if_error: true diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 5c716018..8709a570 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -1,5 +1,6 @@ name: CI -on: [push, pull_request] +on: + pull_request: jobs: cargo-deny: runs-on: ubuntu-22.04 diff --git a/Cargo.lock b/Cargo.lock index 4e7a5495..59da0862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addchain" version = "0.2.0" @@ -105,6 +115,30 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocative" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fac2ce611db8b8cee9b2aa886ca03c924e9da5e5295d0dbd0526e5d0b0710f7" +dependencies = [ + "allocative_derive", + "bumpalo", + "ctor", + "hashbrown 0.14.5", + "num-bigint 0.4.6", +] + +[[package]] +name = "allocative_derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe233a377643e0fc1a56421d7c90acdec45c291b30345eb9f08e8d0ddce5a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -179,6 +213,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "annotate-snippets" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" +dependencies = [ + "unicode-width 0.1.14", +] + [[package]] name = "anstream" version = "0.6.18" @@ -220,12 +263,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -263,15 +306,6 @@ dependencies = [ "derive_arbitrary", ] -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -dependencies = [ - "serde", -] - [[package]] name = "ark-bn254" version = "0.4.0" @@ -474,6 +508,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + [[package]] name = "asn1-rs" version = "0.6.2" @@ -577,9 +620,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" dependencies = [ "async-lock", "cfg-if", @@ -588,7 +631,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.44", + "rustix", "slab", "tracing", "windows-sys 0.59.0", @@ -824,7 +867,7 @@ dependencies = [ "tokenizers", "tokio", "tower 0.5.2", - "tower-http 0.6.4", + "tower-http 0.6.5", "tracing", "tracing-appender", "tracing-opentelemetry", @@ -1041,29 +1084,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "axum-server" -version = "0.6.1" -source = "git+https://github.com/bmwill/axum-server.git?rev=f44323e271afdd1365fd0c8b0a4c0bbdf4956cb7#f44323e271afdd1365fd0c8b0a4c0bbdf4956cb7" -dependencies = [ - "arc-swap", - "bytes", - "futures-util", - "http 1.3.1", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower 0.4.13", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -1137,6 +1157,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bellpepper" version = "0.4.1" @@ -1208,15 +1234,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.8.0" @@ -1246,9 +1287,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -1480,9 +1521,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.22" +version = "1.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" dependencies = [ "jobserver", "libc", @@ -1501,6 +1542,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cfg_aliases" version = "0.2.1" @@ -1586,9 +1633,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", "clap_derive", @@ -1596,9 +1643,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -1625,6 +1672,21 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "cmp_any" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b18233253483ce2f65329a24072ec414db782531bdbb7d0bbc4bd2ce6b7e21" + [[package]] name = "codespan" version = "0.11.1" @@ -1703,7 +1765,7 @@ dependencies = [ [[package]] name = "consensus-config" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "fastcrypto 0.1.8", "mysten-network", @@ -1784,9 +1846,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -1955,6 +2017,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ctr" version = "0.9.2" @@ -2137,6 +2209,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "debugserver-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf6834a70ed14e8e4e41882df27190bea150f1f6ecf461f1033f8739cd8af4a" +dependencies = [ + "schemafy", + "serde", + "serde_json", +] + [[package]] name = "der" version = "0.6.1" @@ -2289,12 +2372,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ + "convert_case 0.6.0", "proc-macro2", "quote", "syn 2.0.101", "unicode-xid", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -2378,6 +2468,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "display_container" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a110a75c96bedec8e65823dea00a1d710288b7a369d95fd8a0f5127639466fa" +dependencies = [ + "either", + "indenter", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -2416,6 +2516,26 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dupe" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed2bc011db9c93fbc2b6cdb341a53737a55bafb46dbb74cf6764fc33a2fbf9c" +dependencies = [ + "dupe_derive", +] + +[[package]] +name = "dupe_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e195b4945e88836d826124af44fdcb262ec01ef94d44f14f4fb5103f19892a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "dyn-clone" version = "1.0.19" @@ -2537,6 +2657,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -2552,6 +2681,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -2567,7 +2702,7 @@ dependencies = [ [[package]] name = "enum-compat-util" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "serde_yaml 0.8.26", ] @@ -2599,16 +2734,31 @@ dependencies = [ "typeid", ] +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] + [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "esaxx-rs" version = "0.1.10" @@ -2631,9 +2781,9 @@ dependencies = [ [[package]] name = "ethnum" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0939f82868b77ef93ce3c3c3daf2b3c526b456741da5a1a4559e590965b6026b" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" [[package]] name = "event-listener" @@ -2848,6 +2998,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "ff" version = "0.12.1" @@ -2922,6 +3083,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.1" @@ -3131,17 +3298,27 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generator" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", "rustversion", - "windows 0.58.0", + "windows 0.61.1", ] [[package]] @@ -3365,15 +3542,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -3596,8 +3767,8 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" -source = "git+https://github.com/rustls/hyper-rustls?branch=main#8f9728a8cb9b5c52b0ef23169dd968e98b8ef42a" +version = "0.27.6" +source = "git+https://github.com/rustls/hyper-rustls?branch=main#e6a23710aa02b81ccf03d54801df8faace53eb68" dependencies = [ "http 1.3.1", "hyper", @@ -3609,7 +3780,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 0.26.11", + "webpki-roots 1.0.0", ] [[package]] @@ -3643,22 +3814,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.3.1", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -3673,7 +3850,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core 0.61.2", ] [[package]] @@ -3734,9 +3911,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", @@ -3750,9 +3927,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" @@ -3964,6 +4141,15 @@ dependencies = [ "similar", ] +[[package]] +name = "inventory" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -3992,6 +4178,17 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -4313,6 +4510,37 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lalrpop" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +dependencies = [ + "ascii-canvas", + "bit-set 0.5.3", + "diff", + "ena", + "is-terminal", + "itertools 0.10.5", + "lalrpop-util", + "petgraph 0.6.5", + "regex", + "regex-syntax 0.6.29", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +dependencies = [ + "regex", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -4322,6 +4550,15 @@ dependencies = [ "spin", ] +[[package]] +name = "lcov" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ccfa6d5e585a884db65b37f38184e4364eaf74d884ac35d0a90fe9baf80b723" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "leb128" version = "0.2.5" @@ -4336,9 +4573,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", "windows-targets 0.53.0", @@ -4769,7 +5006,7 @@ dependencies = [ "thiserror 2.0.12", "tracing", "yamux 0.12.1", - "yamux 0.13.4", + "yamux 0.13.5", ] [[package]] @@ -4778,7 +5015,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", ] @@ -4809,30 +5046,24 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linkme" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22d227772b5999ddc0690e733f734f95ca05387e329c4084fe65678c51198ffe" +checksum = "a1b1703c00b2a6a70738920544aa51652532cacddfec2e162d2e29eae01e665c" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a98813fa0073a317ed6a8055dcd4722a49d9b862af828ee68449adb799b6be" +checksum = "04d55ca5d5a14363da83bf3c33874b8feaa34653e760d5216d7ef9829c88001a" dependencies = [ "proc-macro2", "quote", "syn 2.0.101", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -4847,9 +5078,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -4865,13 +5096,36 @@ dependencies = [ ] [[package]] -name = "loki-api" -version = "0.1.3" +name = "logos" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdc38a304f59a03e6efa3876766a48c70a766a93f88341c3fff4212834b8e327" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" dependencies = [ - "prost", - "prost-types", + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + +[[package]] +name = "loki-api" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdc38a304f59a03e6efa3876766a48c70a766a93f88341c3fff4212834b8e327" +dependencies = [ + "prost", + "prost-types", ] [[package]] @@ -4911,6 +5165,19 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lsp-types" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + [[package]] name = "lsp-types" version = "0.95.1" @@ -4926,9 +5193,9 @@ dependencies = [ [[package]] name = "macro_rules_attribute" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" dependencies = [ "macro_rules_attribute-proc_macro", "paste", @@ -4936,9 +5203,15 @@ dependencies = [ [[package]] name = "macro_rules_attribute-proc_macro" -version = "0.2.0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" + +[[package]] +name = "maplit" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "matchers" @@ -4983,6 +5256,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -5016,13 +5298,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5068,7 +5350,7 @@ dependencies = [ [[package]] name = "move-abstract-interpreter" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "move-binary-format", "move-bytecode-verifier-meter", @@ -5077,12 +5359,12 @@ dependencies = [ [[package]] name = "move-abstract-stack" version = "0.0.1" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" [[package]] name = "move-binary-format" version = "0.0.3" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "enum-compat-util", @@ -5097,12 +5379,12 @@ dependencies = [ [[package]] name = "move-borrow-graph" version = "0.0.1" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" [[package]] name = "move-bytecode-source-map" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -5118,20 +5400,20 @@ dependencies = [ [[package]] name = "move-bytecode-utils" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "indexmap 2.9.0", "move-binary-format", "move-core-types", - "petgraph", + "petgraph 0.5.1", "serde-reflection", ] [[package]] name = "move-bytecode-verifier" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "move-abstract-interpreter", "move-abstract-stack", @@ -5140,13 +5422,13 @@ dependencies = [ "move-bytecode-verifier-meter", "move-core-types", "move-vm-config", - "petgraph", + "petgraph 0.5.1", ] [[package]] name = "move-bytecode-verifier-meter" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "move-binary-format", "move-core-types", @@ -5156,7 +5438,7 @@ dependencies = [ [[package]] name = "move-command-line-common" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -5177,7 +5459,7 @@ dependencies = [ [[package]] name = "move-compiler" version = "0.0.1" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -5186,7 +5468,7 @@ dependencies = [ "dunce", "hex", "insta", - "lsp-types", + "lsp-types 0.95.1", "move-binary-format", "move-borrow-graph", "move-bytecode-source-map", @@ -5198,7 +5480,7 @@ dependencies = [ "move-proc-macros", "move-symbol-pool", "once_cell", - "petgraph", + "petgraph 0.5.1", "rayon", "regex", "serde", @@ -5212,7 +5494,7 @@ dependencies = [ [[package]] name = "move-core-types" version = "0.0.4" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -5236,7 +5518,7 @@ dependencies = [ [[package]] name = "move-coverage" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -5244,20 +5526,23 @@ dependencies = [ "codespan", "colored", "indexmap 2.9.0", + "lcov", "move-abstract-interpreter", "move-binary-format", "move-bytecode-source-map", "move-command-line-common", + "move-compiler", "move-core-types", "move-ir-types", - "petgraph", + "move-trace-format", + "petgraph 0.5.1", "serde", ] [[package]] name = "move-disassembler" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -5278,7 +5563,7 @@ dependencies = [ [[package]] name = "move-ir-to-bytecode" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "codespan-reporting", @@ -5296,7 +5581,7 @@ dependencies = [ [[package]] name = "move-ir-to-bytecode-syntax" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "hex", @@ -5309,7 +5594,7 @@ dependencies = [ [[package]] name = "move-ir-types" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "hex", "move-command-line-common", @@ -5322,7 +5607,7 @@ dependencies = [ [[package]] name = "move-proc-macros" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "enum-compat-util", "quote", @@ -5332,17 +5617,29 @@ dependencies = [ [[package]] name = "move-symbol-pool" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "once_cell", "phf", "serde", ] +[[package]] +name = "move-trace-format" +version = "0.0.1" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" +dependencies = [ + "move-binary-format", + "move-core-types", + "serde", + "serde_json", + "zstd", +] + [[package]] name = "move-vm-config" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "move-binary-format", "once_cell", @@ -5351,7 +5648,7 @@ dependencies = [ [[package]] name = "move-vm-profiler" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "move-vm-config", "once_cell", @@ -5363,7 +5660,7 @@ dependencies = [ [[package]] name = "move-vm-test-utils" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "move-binary-format", @@ -5377,7 +5674,7 @@ dependencies = [ [[package]] name = "move-vm-types" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "bcs", "move-binary-format", @@ -5499,7 +5796,7 @@ dependencies = [ [[package]] name = "mysten-common" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "antithesis_sdk", "anyhow", @@ -5509,13 +5806,10 @@ dependencies = [ "mysten-metrics", "once_cell", "parking_lot", - "prometheus", "rand 0.8.5", "reqwest", "snap", "sui-macros", - "sui-tls", - "sui-types", "tempfile", "tokio", "tracing", @@ -5524,7 +5818,7 @@ dependencies = [ [[package]] name = "mysten-metrics" version = "0.7.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "async-trait", "axum 0.8.4", @@ -5545,7 +5839,7 @@ dependencies = [ [[package]] name = "mysten-network" version = "0.2.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anemo", "anemo-tower", @@ -5684,6 +5978,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.26.4" @@ -5695,6 +6004,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", +] + [[package]] name = "no-std-compat" version = "0.4.1" @@ -5867,11 +6188,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -5916,7 +6237,7 @@ name = "nvml-wrapper" version = "0.10.0" source = "git+https://github.com/atoma-network/nvml-wrapper.git?branch=main#0d416436404473bc11795dacc1c0c5a995d9aa09" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libloading", "nvml-wrapper-sys", "static_assertions", @@ -5997,13 +6318,19 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "libc", "once_cell", "onig_sys", @@ -6011,9 +6338,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -6027,11 +6354,11 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -6059,9 +6386,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.108" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -6292,9 +6619,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -6302,9 +6629,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -6319,7 +6646,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77144664f6aac5f629d7efa815f5098a054beeeca6ccafee5ec453fd2b0c53f9" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "ciborium", "coset", "data-encoding", @@ -6458,10 +6785,20 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ - "fixedbitset", + "fixedbitset 0.2.0", "indexmap 1.9.3", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset 0.4.2", + "indexmap 2.9.0", +] + [[package]] name = "phf" version = "0.11.3" @@ -6587,15 +6924,15 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polling" -version = "3.7.4" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", - "rustix 0.38.44", + "rustix", "tracing", "windows-sys 0.59.0", ] @@ -6653,6 +6990,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "primeorder" version = "0.13.6" @@ -6789,7 +7132,7 @@ dependencies = [ [[package]] name = "prometheus-closure-metric" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "prometheus", @@ -6828,9 +7171,9 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.9.0", + "bit-set 0.8.0", + "bit-vec 0.8.0", + "bitflags 2.9.1", "lazy_static", "num-traits", "rand 0.8.5", @@ -6963,7 +7306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", - "cfg_aliases", + "cfg_aliases 0.2.1", "futures-io", "pin-project-lite", "quinn-proto", @@ -7004,7 +7347,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", "once_cell", "socket2", @@ -7039,6 +7382,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -7122,7 +7475,7 @@ version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -7186,7 +7539,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -7287,9 +7640,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" dependencies = [ "base64 0.22.1", "bytes", @@ -7316,33 +7669,31 @@ dependencies = [ "quinn", "rustls", "rustls-native-certs", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", "tokio-util", "tower 0.5.2", + "tower-http 0.6.5", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.11", - "windows-registry", + "webpki-roots 1.0.0", ] [[package]] name = "resolv-conf" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302" +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "rfc6979" @@ -7405,7 +7756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "serde_derive", ] @@ -7479,16 +7830,16 @@ dependencies = [ "netlink-packet-utils", "netlink-proto", "netlink-sys", - "nix", + "nix 0.26.4", "thiserror 1.0.69", "tokio", ] [[package]] name = "rust-embed" -version = "8.7.1" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e425e204264b144d4c929d126d0de524b40a961686414bab5040f7465c71be" +checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -7497,9 +7848,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.7.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf418c9a2e3f6663ca38b8a7134cc2c2167c9d69688860e8961e3faa731702e" +checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" dependencies = [ "proc-macro2", "quote", @@ -7510,9 +7861,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.7.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d55b95147fe01265d06b3955db798bdaed52e60e2211c41137701b3aba8e21" +checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" dependencies = [ "sha2 0.10.9", "walkdir", @@ -7580,29 +7931,16 @@ dependencies = [ "nom", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] @@ -7658,7 +7996,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -7702,9 +8040,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rusty-fork" @@ -7718,6 +8056,28 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rustyline" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix 0.28.0", + "radix_trie", + "unicode-segmentation", + "unicode-width 0.1.14", + "utf8parse", + "windows-sys 0.52.0", +] + [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -7762,6 +8122,48 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemafy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aea5ba40287dae331f2c48b64dbc8138541f5e97ee8793caa7948c1f31d86d5" +dependencies = [ + "Inflector", + "schemafy_core", + "schemafy_lib", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "syn 1.0.109", +] + +[[package]] +name = "schemafy_core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41781ae092f4fd52c9287efb74456aea0d3b90032d2ecad272bd14dbbcb0511b" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "schemafy_lib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e953db32579999ca98c451d80801b6f6a7ecba6127196c5387ec0774c528befa" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "schemafy_core", + "serde", + "serde_derive", + "serde_json", + "syn 1.0.109", +] + [[package]] name = "schemars" version = "0.8.22" @@ -7858,7 +8260,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -7871,8 +8273,8 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -8267,7 +8669,7 @@ dependencies = [ [[package]] name = "shared-crypto" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "bcs", "eyre", @@ -8386,18 +8788,18 @@ dependencies = [ [[package]] name = "snafu" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" +checksum = "320b01e011bf8d5d7a4a4a4be966d9160968935849c83b918827f6a435e7f627" dependencies = [ "snafu-derive", ] [[package]] name = "snafu-derive" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" +checksum = "1961e2ef424c1424204d3a5d6975f934f56b6d50ff5732382d84ebf460e147f7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -8430,9 +8832,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -8597,7 +8999,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "bytes", "crc", @@ -8639,7 +9041,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "crc", "dotenvy", @@ -8711,12 +9113,114 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "starlark" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f53849859f05d9db705b221bd92eede93877fd426c1b4a3c3061403a5912a8f" +dependencies = [ + "allocative", + "anyhow", + "bumpalo", + "cmp_any", + "debugserver-types", + "derivative", + "derive_more 1.0.0", + "display_container", + "dupe", + "either", + "erased-serde", + "hashbrown 0.14.5", + "inventory", + "itertools 0.13.0", + "maplit", + "memoffset", + "num-bigint 0.4.6", + "num-traits", + "once_cell", + "paste", + "ref-cast", + "regex", + "rustyline", + "serde", + "serde_json", + "starlark_derive", + "starlark_map", + "starlark_syntax", + "static_assertions", + "strsim 0.10.0", + "textwrap", + "thiserror 1.0.69", +] + +[[package]] +name = "starlark_derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe58bc6c8b7980a1fe4c9f8f48200c3212db42ebfe21ae6a0336385ab53f082a" +dependencies = [ + "dupe", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "starlark_map" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92659970f120df0cc1c0bb220b33587b7a9a90e80d4eecc5c5af5debb950173d" +dependencies = [ + "allocative", + "dupe", + "equivalent", + "fxhash", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "starlark_syntax" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe53b3690d776aafd7cb6b9fed62d94f83280e3b87d88e3719cc0024638461b3" +dependencies = [ + "allocative", + "annotate-snippets", + "anyhow", + "derivative", + "derive_more 1.0.0", + "dupe", + "lalrpop", + "lalrpop-util", + "logos", + "lsp-types 0.94.1", + "memchr", + "num-bigint 0.4.6", + "num-traits", + "once_cell", + "starlark_map", + "thiserror 1.0.69", +] + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -8799,7 +9303,7 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "sui-config" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anemo", "anyhow", @@ -8818,19 +9322,22 @@ dependencies = [ "rand 0.8.5", "reqwest", "serde", + "serde_json", "serde_with", "serde_yaml 0.8.26", + "starlark", "sui-keys", "sui-protocol-config", "sui-rpc-api", "sui-types", + "thiserror 1.0.69", "tracing", ] [[package]] name = "sui-enum-compat-util" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "serde_yaml 0.8.26", ] @@ -8838,7 +9345,7 @@ dependencies = [ [[package]] name = "sui-http" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "bytes", "http 1.3.1", @@ -8858,7 +9365,7 @@ dependencies = [ [[package]] name = "sui-json" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -8875,7 +9382,7 @@ dependencies = [ [[package]] name = "sui-json-rpc-api" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "fastcrypto 0.1.8", @@ -8895,7 +9402,7 @@ dependencies = [ [[package]] name = "sui-json-rpc-types" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bcs", @@ -8927,7 +9434,7 @@ dependencies = [ [[package]] name = "sui-keys" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "bip32", @@ -8946,7 +9453,7 @@ dependencies = [ [[package]] name = "sui-macros" version = "0.7.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "futures", "once_cell", @@ -8956,8 +9463,8 @@ dependencies = [ [[package]] name = "sui-open-rpc" -version = "1.47.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +version = "1.49.1" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "bcs", "schemars", @@ -8969,7 +9476,7 @@ dependencies = [ [[package]] name = "sui-open-rpc-macros" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "derive-syn-parse", "itertools 0.13.0", @@ -8982,7 +9489,7 @@ dependencies = [ [[package]] name = "sui-package-resolver" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "async-trait", "bcs", @@ -9001,7 +9508,7 @@ dependencies = [ [[package]] name = "sui-proc-macros" version = "0.7.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "msim-macros", "proc-macro2", @@ -9013,7 +9520,7 @@ dependencies = [ [[package]] name = "sui-protocol-config" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "clap", "move-vm-config", @@ -9028,7 +9535,7 @@ dependencies = [ [[package]] name = "sui-protocol-config-macros" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "proc-macro2", "quote", @@ -9038,7 +9545,7 @@ dependencies = [ [[package]] name = "sui-rpc-api" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "async-stream", @@ -9064,7 +9571,6 @@ dependencies = [ "serde_with", "sui-protocol-config", "sui-sdk-types", - "sui-transaction-builder 0.0.4", "sui-types", "tap", "thiserror 1.0.69", @@ -9081,8 +9587,8 @@ dependencies = [ [[package]] name = "sui-sdk" -version = "1.47.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +version = "1.49.1" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "async-trait", @@ -9105,7 +9611,7 @@ dependencies = [ "sui-json-rpc-api", "sui-json-rpc-types", "sui-keys", - "sui-transaction-builder 0.0.0", + "sui-transaction-builder", "sui-types", "thiserror 1.0.69", "tokio", @@ -9131,32 +9637,10 @@ dependencies = [ "winnow", ] -[[package]] -name = "sui-tls" -version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" -dependencies = [ - "anyhow", - "arc-swap", - "axum 0.8.4", - "axum-server", - "ed25519", - "fastcrypto 0.1.8", - "pkcs8 0.10.2", - "rcgen", - "reqwest", - "rustls", - "rustls-webpki 0.103.3", - "tokio", - "tokio-rustls", - "tower-layer", - "x509-parser 0.17.0", -] - [[package]] name = "sui-transaction-builder" version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anyhow", "async-trait", @@ -9170,24 +9654,10 @@ dependencies = [ "sui-types", ] -[[package]] -name = "sui-transaction-builder" -version = "0.0.4" -source = "git+https://github.com/MystenLabs/sui-rust-sdk.git?rev=83ff809bc11cbabda21b60130e1f5420170548bf#83ff809bc11cbabda21b60130e1f5420170548bf" -dependencies = [ - "base64ct", - "bcs", - "serde", - "serde_json", - "serde_with", - "sui-sdk-types", - "thiserror 2.0.12", -] - [[package]] name = "sui-types" version = "0.1.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "anemo", "anyhow", @@ -9215,6 +9685,7 @@ dependencies = [ "move-core-types", "move-vm-profiler", "move-vm-test-utils", + "mysten-common", "mysten-metrics", "mysten-network", "nonempty", @@ -9328,7 +9799,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9388,10 +9859,21 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix", "windows-sys 0.59.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -9407,10 +9889,19 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.7", + "rustix", "windows-sys 0.59.0", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width 0.1.14", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -9630,7 +10121,7 @@ dependencies = [ [[package]] name = "tokio-rustls" version = "0.26.2" -source = "git+https://github.com/rustls/tokio-rustls?branch=main#8092a899759480b86544207c434b58ace3083346" +source = "git+https://github.com/rustls/tokio-rustls?branch=main#6a775e132632340d7f788cf1eba1f618d0d9e7b2" dependencies = [ "rustls", "tokio", @@ -9897,7 +10388,7 @@ checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", "base64 0.21.7", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-core", "futures-util", @@ -9922,14 +10413,18 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" +checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", + "futures-util", "http 1.3.1", + "http-body", + "iri-string", "pin-project-lite", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -10110,7 +10605,7 @@ dependencies = [ [[package]] name = "typed-store-error" version = "0.4.0" -source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.47.0#f3e72b60708b682f1d286b8d218977c4338e39d9" +source = "git+https://github.com/mystenlabs/sui?tag=testnet-v1.49.1#3b1d6b3bd63f175b774da557f89af3619b74d783" dependencies = [ "serde", "thiserror 1.0.69", @@ -10400,13 +10895,15 @@ dependencies = [ [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", + "js-sys", "rand 0.9.1", "serde", + "wasm-bindgen", ] [[package]] @@ -10741,12 +11238,24 @@ dependencies = [ [[package]] name = "windows" -version = "0.58.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -10773,46 +11282,33 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", "windows-link", - "windows-result 0.3.2", - "windows-strings 0.4.0", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "windows-future" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "windows-core 0.61.2", + "windows-link", + "windows-threading", ] [[package]] name = "windows-implement" -version = "0.58.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", @@ -10841,17 +11337,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -10869,13 +11354,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result 0.3.2", + "windows-result 0.3.4", "windows-strings 0.3.1", "windows-targets 0.53.0", ] @@ -10891,32 +11386,13 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-strings" version = "0.3.1" @@ -10928,9 +11404,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -11033,6 +11509,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -11238,7 +11723,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -11373,16 +11858,16 @@ dependencies = [ [[package]] name = "yamux" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17610762a1207ee816c6fadc29220904753648aba0a9ed61c7b8336e80a559c4" +checksum = "3da1acad1c2dc53f0dde419115a38bd8221d8c3e47ae9aeceaf453266d29307e" dependencies = [ "futures", "log", "nohash-hasher", "parking_lot", "pin-project", - "rand 0.8.5", + "rand 0.9.1", "static_assertions", "web-time", ] diff --git a/Cargo.toml b/Cargo.toml index 8b15b490..c152f46e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,8 +70,8 @@ serde_yaml = "0.9.34" serial_test = "3.1.1" sha2 = "0.10.9" sqlx = "0.8.6" -sui-keys = { git = "https://github.com/mystenlabs/sui", package = "sui-keys", tag = "testnet-v1.47.0" } -sui-sdk = { git = "https://github.com/mystenlabs/sui", package = "sui-sdk", tag = "testnet-v1.47.0" } +sui-keys = { git = "https://github.com/mystenlabs/sui", package = "sui-keys", tag = "testnet-v1.49.1" } +sui-sdk = { git = "https://github.com/mystenlabs/sui", package = "sui-sdk", tag = "testnet-v1.49.1" } sysinfo = "0.33.1" tempfile = "3.20.0" thiserror = "2.0.12" diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index 16f87281..f28a2321 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -213,11 +213,13 @@ async fn main() -> Result<()> { let keystore = FileBasedKeystore::new(&config.sui.sui_keystore_path().into()) .context("Failed to initialize keystore")?; - let mut wallet_ctx = WalletContext::new( - &PathBuf::from(config.sui.sui_config_path()), - config.sui.request_timeout(), - config.sui.max_concurrent_requests(), - )?; + let mut wallet_ctx = WalletContext::new(&PathBuf::from(config.sui.sui_config_path()))?; + if let Some(request_timeout) = config.sui.request_timeout() { + wallet_ctx = wallet_ctx.with_request_timeout(request_timeout); + } + if let Some(max_concurrent_requests) = config.sui.max_concurrent_requests() { + wallet_ctx = wallet_ctx.with_max_concurrent_requests(max_concurrent_requests); + } let address = wallet_ctx.active_address()?; let address_index = args.address_index.unwrap_or_else(|| { wallet_ctx diff --git a/atoma-sui/src/client.rs b/atoma-sui/src/client.rs index 43ac0237..0e078f34 100644 --- a/atoma-sui/src/client.rs +++ b/atoma-sui/src/client.rs @@ -86,11 +86,13 @@ impl Client { pub async fn new(config: SuiConfig) -> Result { let sui_config_path = config.sui_config_path(); let sui_config_path = Path::new(&sui_config_path); - let mut wallet_ctx = WalletContext::new( - sui_config_path, - config.request_timeout(), - config.max_concurrent_requests(), - )?; + let mut wallet_ctx = WalletContext::new(sui_config_path)?; + if let Some(request_timeout) = config.request_timeout() { + wallet_ctx = wallet_ctx.with_request_timeout(request_timeout); + } + if let Some(max_concurrent_requests) = config.max_concurrent_requests() { + wallet_ctx = wallet_ctx.with_max_concurrent_requests(max_concurrent_requests); + } let active_address = wallet_ctx.active_address()?; info!("Current active address: {}", active_address); let node_badge = utils::get_node_badge( From 9b67457a3f2238bfa53dea21b96a64655f338dc6 Mon Sep 17 00:00:00 2001 From: Martin Stefcek <35243812+Cifko@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:04:11 +0200 Subject: [PATCH 11/15] feat: add max number of queued requests configuration and update request handling (#656) --- atoma-bin/atoma_node.rs | 3 ++- atoma-service/src/config.rs | 4 ++++ atoma-service/src/handlers/chat_completions.rs | 10 ++++++++-- atoma-service/src/handlers/completions.rs | 10 ++++++++-- atoma-service/src/handlers/mod.rs | 10 ++++++++++ atoma-service/src/middleware.rs | 11 ++++++++++- atoma-service/src/server.rs | 7 +++++-- atoma-service/src/tests.rs | 3 ++- config.example.toml | 1 + 9 files changed, 50 insertions(+), 9 deletions(-) diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index f28a2321..ed234a1c 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -375,11 +375,12 @@ async fn main() -> Result<()> { keystore: Arc::new(keystore), address_index, whitelist_sui_addresses_for_fiat: config.service.whitelist_sui_addresses_for_fiat, - too_many_requests: Arc::new(DashSet::new()), + too_many_requests: Arc::new(DashMap::new()), too_many_requests_timeout_ms: u128::from(config.service.too_many_requests_timeout_ms), running_num_requests: Arc::new(RequestCounter::new()), memory_lower_threshold: config.service.memory_lower_threshold, memory_upper_threshold: config.service.memory_upper_threshold, + max_num_queued_requests: config.service.max_num_queued_requests, }; let chat_completions_service_urls = app_state diff --git a/atoma-service/src/config.rs b/atoma-service/src/config.rs index e6725ff3..9e2aa509 100644 --- a/atoma-service/src/config.rs +++ b/atoma-service/src/config.rs @@ -63,8 +63,12 @@ pub struct AtomaServiceConfig { ///Lower threshold for memory usage, if the memory usage goes below this value, the service will not be considered overloaded pub memory_lower_threshold: f64, + /// Upper threshold for memory usage, if the memory usage goes above this value, the service will be considered overloaded pub memory_upper_threshold: f64, + + /// The maximum number of queued requests for each inference service. + pub max_num_queued_requests: f64, } impl AtomaServiceConfig { diff --git a/atoma-service/src/handlers/chat_completions.rs b/atoma-service/src/handlers/chat_completions.rs index 20fa6ae9..f53815c1 100644 --- a/atoma-service/src/handlers/chat_completions.rs +++ b/atoma-service/src/handlers/chat_completions.rs @@ -909,6 +909,7 @@ async fn handle_streaming_response( chat_completions_service_urls, &model.to_lowercase(), state.memory_upper_threshold, + state.max_num_queued_requests, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -916,7 +917,9 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state.too_many_requests.insert(model.to_string()); + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.clone(), @@ -1341,6 +1344,7 @@ pub mod utils { chat_completions_service_url_services, model, state.memory_upper_threshold, + state.max_num_queued_requests, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -1348,7 +1352,9 @@ pub mod utils { endpoint: endpoint.to_string(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state.too_many_requests.insert(model.to_string()); + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.to_string(), diff --git a/atoma-service/src/handlers/completions.rs b/atoma-service/src/handlers/completions.rs index 0d80a52f..4b44ded0 100644 --- a/atoma-service/src/handlers/completions.rs +++ b/atoma-service/src/handlers/completions.rs @@ -882,6 +882,7 @@ async fn handle_streaming_response( chat_completions_service_urls, model, state.memory_upper_threshold, + state.max_num_queued_requests, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -889,7 +890,9 @@ async fn handle_streaming_response( endpoint: endpoint.clone(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state.too_many_requests.insert(model.to_string()); + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.clone(), @@ -1303,6 +1306,7 @@ pub mod utils { completions_service_url_services, model, state.memory_upper_threshold, + state.max_num_queued_requests, ) .await .map_err(|e| AtomaServiceError::ChatCompletionsServiceUnavailable { @@ -1310,7 +1314,9 @@ pub mod utils { endpoint: endpoint.to_string(), })?; if status_code == StatusCode::TOO_MANY_REQUESTS { - state.too_many_requests.insert(model.to_string()); + state + .too_many_requests + .insert(model.to_string(), Instant::now()); return Err(AtomaServiceError::ChatCompletionsServiceUnavailable { message: "Too many requests".to_string(), endpoint: endpoint.to_string(), diff --git a/atoma-service/src/handlers/mod.rs b/atoma-service/src/handlers/mod.rs index 085163dd..ed5dadab 100644 --- a/atoma-service/src/handlers/mod.rs +++ b/atoma-service/src/handlers/mod.rs @@ -1147,6 +1147,7 @@ pub mod inference_service_metrics { chat_completions_service_urls: &[(String, String, usize)], // (url, job, max_concurrent_requests) model: &str, memory_upper_threshold: f64, + max_num_queued_requests: f64, ) -> Result<(String, StatusCode)> { let mut metrics_results = get_all_metrics(chat_completions_service_urls, model) .await @@ -1178,6 +1179,15 @@ pub mod inference_service_metrics { &metric.chat_completions_service_url, metric.max_number_of_running_requests, ) { + if metric.num_queued_requests > max_num_queued_requests { + tracing::debug!( + target = "atoma-service", + level = "debug", + "Number of queued requests for model: {model} is too high: {}", + metric.num_queued_requests + ); + continue; + } if metric.above_upper_threshold_exceeded(memory_upper_threshold) { tracing::debug!( target = "atoma-service", diff --git a/atoma-service/src/middleware.rs b/atoma-service/src/middleware.rs index 701b986a..010d8667 100644 --- a/atoma-service/src/middleware.rs +++ b/atoma-service/src/middleware.rs @@ -1613,7 +1613,16 @@ pub mod utils { model: &str, endpoint: &str, ) -> Result<(), AtomaServiceError> { - if state.too_many_requests.get(model).is_some() { + if let Some(a) = state.too_many_requests.get(model) { + if a.elapsed().as_millis() < state.too_many_requests_timeout_ms { + tracing::debug!( + target = "atoma-service", + level = "debug", + "Model {} is in the `too_many_requests` map, but the elapsed time since the first occurrence is less than the timeout.", + model + ); + return Ok(()); + } let chat_completions_service_urls = state .chat_completions_service_urls .get(&model.to_lowercase()) diff --git a/atoma-service/src/server.rs b/atoma-service/src/server.rs index 7e132116..575870ff 100644 --- a/atoma-service/src/server.rs +++ b/atoma-service/src/server.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Instant}; use atoma_confidential::types::{ ConfidentialComputeDecryptionRequest, ConfidentialComputeDecryptionResponse, @@ -207,7 +207,7 @@ pub struct AppState { pub whitelist_sui_addresses_for_fiat: Vec, /// When was the too many requests triggered for each model. - pub too_many_requests: Arc>, + pub too_many_requests: Arc>, /// The time for which we triiger too many requests since the first occurrence. pub too_many_requests_timeout_ms: u128, @@ -222,6 +222,9 @@ pub struct AppState { /// The lower memory threshold for the node. /// This threshold is used to determine when the node can start accepting requests again. pub memory_lower_threshold: f64, + + /// The maximum number of queued requests for each inference service. + pub max_num_queued_requests: f64, } /// Creates and configures the main router for the application. diff --git a/atoma-service/src/tests.rs b/atoma-service/src/tests.rs index b601a696..c180ab41 100644 --- a/atoma-service/src/tests.rs +++ b/atoma-service/src/tests.rs @@ -341,11 +341,12 @@ mod middleware { address_index: 0, stack_retrieve_sender, whitelist_sui_addresses_for_fiat: vec![], - too_many_requests: Arc::new(DashSet::new()), + too_many_requests: Arc::new(DashMap::new()), too_many_requests_timeout_ms: 0, running_num_requests: Arc::new(RequestCounter::new()), memory_lower_threshold: 1.0, memory_upper_threshold: 1.0, + max_num_queued_requests: 0.0, }, public_key, signature, diff --git a/config.example.toml b/config.example.toml index 1a519842..7a91191c 100644 --- a/config.example.toml +++ b/config.example.toml @@ -46,6 +46,7 @@ image_generations_service_url = "http://image-generations:80" # List of models to be used by the service, the current value here is just a placeholder, please change it to the models you want to deploy environment = "development" # or "production" (for use in sentry, you need to set the Sentry DSN) heartbeat_url = "my-heartbeat-url" +max_num_queued_requests = 1 # Maximum number of queued requests for each inference service, this is used to limit the number of requests that can be queued for each service, if the number of queued requests exceeds this value, the service will be considered overloaded and will not accept new requests memory_lower_threshold = 0.75 # Lower threshold for memory usage, if the memory usage goes below this value, the service will not be considered overloaded memory_upper_threshold = 0.9 # Upper threshold for memory usage, if the memory usage goes above this value, the service will be considered overloaded metrics_update_interval = 30 From 666dc2791b8c1380e2a3d36efb75bb3eaa68c1c3 Mon Sep 17 00:00:00 2001 From: chad Date: Wed, 4 Jun 2025 13:24:55 -0500 Subject: [PATCH 12/15] build: add node kubernetes deployment --- helm/atoma-node/Chart.yaml | 23 ++ helm/atoma-node/README.md | 253 ++++++++++++++++++ .../templates/atoma-node-deployment.yaml | 57 ++++ helm/atoma-node/templates/configmap.yaml | 13 + helm/atoma-node/templates/env-configmap.yaml | 13 + helm/atoma-node/templates/ingress.yaml | 53 ++++ .../templates/sglang-deployment.yaml | 74 +++++ .../templates/sui-config-configmap.yaml | 15 ++ .../atoma-node/templates/vllm-deployment.yaml | 65 +++++ helm/atoma-node/values-dev.yaml | 0 helm/atoma-node/values.yaml | 179 +++++++++++++ helm/infrastructure/Chart.yaml | 23 ++ helm/infrastructure/metallb-config.yaml | 32 +++ helm/infrastructure/values.yaml | 51 ++++ scripts/cleanup-minikube.sh | 29 ++ scripts/setup-minikube.sh | 141 ++++++++++ 16 files changed, 1021 insertions(+) create mode 100644 helm/atoma-node/Chart.yaml create mode 100644 helm/atoma-node/README.md create mode 100644 helm/atoma-node/templates/atoma-node-deployment.yaml create mode 100644 helm/atoma-node/templates/configmap.yaml create mode 100644 helm/atoma-node/templates/env-configmap.yaml create mode 100644 helm/atoma-node/templates/ingress.yaml create mode 100644 helm/atoma-node/templates/sglang-deployment.yaml create mode 100644 helm/atoma-node/templates/sui-config-configmap.yaml create mode 100644 helm/atoma-node/templates/vllm-deployment.yaml create mode 100644 helm/atoma-node/values-dev.yaml create mode 100644 helm/atoma-node/values.yaml create mode 100644 helm/infrastructure/Chart.yaml create mode 100644 helm/infrastructure/metallb-config.yaml create mode 100644 helm/infrastructure/values.yaml create mode 100644 scripts/cleanup-minikube.sh create mode 100644 scripts/setup-minikube.sh diff --git a/helm/atoma-node/Chart.yaml b/helm/atoma-node/Chart.yaml new file mode 100644 index 00000000..467b68ed --- /dev/null +++ b/helm/atoma-node/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: atoma-node +description: A Helm chart for deploying Atoma Node and its dependencies +type: application +version: 0.1.0 +appVersion: "1.0.0" +dependencies: + - name: prometheus + version: "25.0.0" + repository: https://prometheus-community.github.io/helm-charts + condition: prometheus.enabled + - name: grafana + version: "7.0.0" + repository: https://grafana.github.io/helm-charts + condition: grafana.enabled + - name: loki + version: "5.41.3" + repository: https://grafana.github.io/helm-charts + condition: loki.enabled + - name: tempo + version: "1.5.0" + repository: https://grafana.github.io/helm-charts + condition: tempo.enabled diff --git a/helm/atoma-node/README.md b/helm/atoma-node/README.md new file mode 100644 index 00000000..2cb50c8f --- /dev/null +++ b/helm/atoma-node/README.md @@ -0,0 +1,253 @@ +# Atoma Node Helm Chart + +This Helm chart deploys the Atoma Node and its dependencies, including VLLM and SGLang inference servers, along with monitoring stack (Prometheus, Grafana, Loki, and Tempo). + +## Prerequisites + +- Kubernetes cluster with GPU support (for VLLM and SGLang) +- Helm 3.x +- NVIDIA device plugin installed in the cluster +- Storage class with sufficient capacity for persistent volumes +- MetalLB (for LoadBalancer services) +- Ingress NGINX (for external access) +- Cert-Manager (for TLS certificates) + +## Infrastructure Components + +The infrastructure components are managed by a separate Helm chart in the `infrastructure` directory. These components include: + +- MetalLB: For LoadBalancer services +- NVIDIA Device Plugin: For GPU support +- Ingress NGINX: For external access +- Cert-Manager: For TLS certificate management + +To install the infrastructure components: + +```bash +# Add required repositories +helm repo add metallb https://metallb.github.io/metallb +helm repo add nvidia https://nvidia.github.io/k8s-device-plugin +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm repo add jetstack https://charts.jetstack.io +helm repo update + +# Install infrastructure +helm dependency update ./helm/infrastructure +helm install infrastructure ./helm/infrastructure \ + --namespace infrastructure \ + --create-namespace +``` + +## Local Testing with Minikube + +For local testing, you can use the provided scripts to set up a Minikube environment: + +1. Make the scripts executable: +```bash +chmod +x scripts/setup-minikube.sh +chmod +x scripts/cleanup-minikube.sh +``` + +2. Run the setup script: +```bash +./scripts/setup-minikube.sh +``` + +3. Install the Atoma Node chart: +```bash +helm install atoma-node ./helm/atoma-node -f values-local.yaml -n atoma +``` + +4. To clean up: +```bash +./scripts/cleanup-minikube.sh +``` + +Note: The Minikube setup script will: +- Start Minikube with GPU support +- Install all required infrastructure components +- Configure MetalLB for LoadBalancer services +- Create necessary namespaces and secrets +- Generate a values file for local development + +## Installation + +1. Add the required Helm repositories: + +```bash +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo add grafana https://grafana.github.io/helm-charts +helm repo update +``` + +2. Create a values file for your environment (e.g., `my-values.yaml`): + +```yaml +# Example values file +atomaNode: + image: + repository: ghcr.io/atoma-network/atoma-node + tag: latest + + config: + environment: "production" + heartbeatUrl: "your-heartbeat-url" + # Add other configuration as needed + +vllm: + enabled: true + replicas: 8 # Adjust based on your GPU count + model: "your-model-name" + maxModelLen: 4096 + +sglang: + enabled: true + modelPath: "your-model-path" + +# Configure monitoring stack +prometheus: + enabled: true + server: + persistentVolume: + size: 50Gi + +grafana: + enabled: true + adminPassword: "your-secure-password" + +loki: + enabled: true + persistence: + size: 50Gi + +tempo: + enabled: true + persistence: + size: 50Gi +``` + +3. Create required secrets: + +```bash +# Create SUI config secret +kubectl create secret generic atoma-node-sui-config \ + --from-file=client.yaml=/path/to/sui/client.yaml \ + --from-file=sui.keystore=/path/to/sui/sui.keystore + +# Create SGLang secrets +kubectl create secret generic atoma-node-sglang-secrets \ + --from-literal=hf-token=your-huggingface-token +``` + +4. Install the chart: + +```bash +helm install atoma-node ./helm/atoma-node \ + -f my-values.yaml \ + --namespace atoma \ + --create-namespace +``` + +## Configuration + +### Atoma Node + +The Atoma Node can be configured through the `atomaNode` section in values: + +- `image`: Container image configuration +- `resources`: Resource requests and limits +- `service`: Service configuration +- `config`: Application configuration +- `persistence`: Storage configuration + +### VLLM + +VLLM inference servers can be configured through the `vllm` section: + +- `enabled`: Enable/disable VLLM deployment +- `replicas`: Number of VLLM instances +- `resources`: Resource configuration including GPU requests +- `model`: Model name to load +- `maxModelLen`: Maximum model context length + +### SGLang + +SGLang inference server can be configured through the `sglang` section: + +- `enabled`: Enable/disable SGLang deployment +- `resources`: Resource configuration including GPU requests +- `modelPath`: Path to the model + +### Monitoring Stack + +The monitoring stack includes: + +- Prometheus: Metrics collection +- Grafana: Metrics visualization +- Loki: Log aggregation +- Tempo: Distributed tracing + +Each component can be enabled/disabled and configured through their respective sections in values. + +## Accessing Services + +- Atoma Node API: `http://atoma-node:3000` +- Grafana: `http://grafana:3000` +- Prometheus: `http://prometheus:9090` +- Loki: `http://loki:3100` +- Tempo: `http://tempo:3200` + +## Upgrading + +To upgrade the deployment: + +```bash +helm upgrade atoma-node ./helm/atoma-node \ + -f my-values.yaml \ + --namespace atoma +``` + +## Uninstalling + +To uninstall the deployment: + +```bash +helm uninstall atoma-node --namespace atoma +``` + +## Troubleshooting + +1. Check pod status: +```bash +kubectl get pods -n atoma +``` + +2. Check pod logs: +```bash +kubectl logs -n atoma +``` + +3. Check persistent volume claims: +```bash +kubectl get pvc -n atoma +``` + +4. Check services: +```bash +kubectl get svc -n atoma +``` + +5. Check GPU availability: +```bash +kubectl describe node | grep nvidia.com/gpu +``` + +## Notes + +- Ensure your cluster has sufficient GPU resources for VLLM and SGLang +- Adjust resource requests and limits based on your cluster capacity +- Configure appropriate storage classes for persistent volumes +- Set up proper network policies for security +- Consider using an ingress controller for external access +- For local testing, use the provided Minikube scripts +- Make sure MetalLB is properly configured for LoadBalancer services diff --git a/helm/atoma-node/templates/atoma-node-deployment.yaml b/helm/atoma-node/templates/atoma-node-deployment.yaml new file mode 100644 index 00000000..3470ad03 --- /dev/null +++ b/helm/atoma-node/templates/atoma-node-deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-atoma-node + labels: + app: atoma-node + release: {{ .Release.Name }} +spec: + replicas: 1 + selector: + matchLabels: + app: atoma-node + release: {{ .Release.Name }} + template: + metadata: + labels: + app: atoma-node + release: {{ .Release.Name }} + spec: + containers: + - name: atoma-node + image: "{{ .Values.atomaNode.image.repository }}:{{ .Values.atomaNode.image.tag }}" + imagePullPolicy: {{ .Values.atomaNode.image.pullPolicy }} + ports: + - name: service + containerPort: {{ .Values.atomaNode.service.port }} + - name: daemon + containerPort: {{ .Values.atomaNode.service.daemonPort }} + - name: p2p + containerPort: {{ .Values.atomaNode.service.p2pPort }} + resources: + {{- toYaml .Values.atomaNode.resources | nindent 12 }} + envFrom: + - configMapRef: + name: {{ .Release.Name }}-env + volumeMounts: + - name: config + mountPath: /app/config.toml + subPath: config.toml + - name: data + mountPath: /app/data + - name: logs + mountPath: /app/logs + - name: sui-config + mountPath: /root/.sui/sui_config + volumes: + - name: config + configMap: + name: {{ .Release.Name }}-config + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-atoma-node-data + - name: logs + emptyDir: {} + - name: sui-config + configMap: + name: {{ .Release.Name }}-sui-config \ No newline at end of file diff --git a/helm/atoma-node/templates/configmap.yaml b/helm/atoma-node/templates/configmap.yaml new file mode 100644 index 00000000..260420a9 --- /dev/null +++ b/helm/atoma-node/templates/configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-config + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} +data: + config.toml: |- +{{ .Files.Get "files/config.toml" | indent 4 }} + environment: {{ .Values.atomaNode.config.environment | quote }} + log_level: {{ .Values.atomaNode.config.logLevel | quote }} \ No newline at end of file diff --git a/helm/atoma-node/templates/env-configmap.yaml b/helm/atoma-node/templates/env-configmap.yaml new file mode 100644 index 00000000..df0543f9 --- /dev/null +++ b/helm/atoma-node/templates/env-configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-env + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} +data: + .env: |- +{{- range $key, $value := .Values.atomaNode.env }} +{{ $key }}={{ $value }} +{{- end }} \ No newline at end of file diff --git a/helm/atoma-node/templates/ingress.yaml b/helm/atoma-node/templates/ingress.yaml new file mode 100644 index 00000000..a56f7d3f --- /dev/null +++ b/helm/atoma-node/templates/ingress.yaml @@ -0,0 +1,53 @@ +{{- if .Values.atomaNode.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-ingress + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- with .Values.atomaNode.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.atomaNode.ingress.className }} + tls: + - hosts: + {{- range .Values.atomaNode.ingress.hosts }} + - {{ .host }} + {{- end }} + secretName: {{ .Release.Name }}-tls + rules: + {{- range .Values.atomaNode.ingress.hosts }} + - host: {{ .host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ $.Release.Name }}-atoma-node + port: + number: {{ $.Values.atomaNode.service.port }} + {{- if $.Values.vllm.enabled }} + - path: /vllm + pathType: Prefix + backend: + service: + name: {{ $.Release.Name }}-vllm-0 + port: + number: {{ $.Values.vllm.service.port }} + {{- end }} + {{- if $.Values.sglang.enabled }} + - path: /sglang + pathType: Prefix + backend: + service: + name: {{ $.Release.Name }}-sglang + port: + number: {{ $.Values.sglang.service.port }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/atoma-node/templates/sglang-deployment.yaml b/helm/atoma-node/templates/sglang-deployment.yaml new file mode 100644 index 00000000..4f1443f7 --- /dev/null +++ b/helm/atoma-node/templates/sglang-deployment.yaml @@ -0,0 +1,74 @@ +{{- if .Values.sglang.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-sglang + labels: + app: sglang + release: {{ .Release.Name }} +spec: + replicas: 1 + selector: + matchLabels: + app: sglang + release: {{ .Release.Name }} + template: + metadata: + labels: + app: sglang + release: {{ .Release.Name }} + spec: + containers: + - name: sglang + image: "{{ .Values.sglang.image.repository }}:{{ .Values.sglang.image.tag }}" + imagePullPolicy: {{ .Values.sglang.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.sglang.service.port }} + resources: + {{- toYaml .Values.sglang.resources | nindent 12 }} + env: + - name: HF_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-sglang-secrets + key: hf-token + command: + - python3 + - -m + - sglang.launch_server + args: + - --model-path + - {{ .Values.sglang.modelPath }} + - --tp + - "8" + - --enable-dp-attention + - --dp + - "8" + - --trust-remote-code + - --max-running-requests + - "128" + - --enable-metrics + - --host + - "0.0.0.0" + - --port + - {{ .Values.sglang.service.port | quote }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-sglang + labels: + app: sglang + release: {{ .Release.Name }} +spec: + type: {{ .Values.sglang.service.type }} + ports: + - port: {{ .Values.sglang.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: sglang + release: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/helm/atoma-node/templates/sui-config-configmap.yaml b/helm/atoma-node/templates/sui-config-configmap.yaml new file mode 100644 index 00000000..312a6fed --- /dev/null +++ b/helm/atoma-node/templates/sui-config-configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-sui-config + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} +data: + client.yaml: |- +{{ .Files.Get "files/sui_config/client.yaml" | indent 4 }} + sui.keystore: |- +{{ .Files.Get "files/sui_config/sui.keystore" | indent 4 }} + sui.aliases: |- +{{ .Files.Get "files/sui_config/sui.aliases" | indent 4 }} \ No newline at end of file diff --git a/helm/atoma-node/templates/vllm-deployment.yaml b/helm/atoma-node/templates/vllm-deployment.yaml new file mode 100644 index 00000000..75fc9cb1 --- /dev/null +++ b/helm/atoma-node/templates/vllm-deployment.yaml @@ -0,0 +1,65 @@ +{{- if .Values.vllm.enabled }} +{{- range $i, $e := until (.Values.vllm.replicas | int) }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Release.Name }}-vllm-{{ $i }} + labels: + app: vllm + instance: {{ $i }} + release: {{ $.Release.Name }} +spec: + replicas: 1 + selector: + matchLabels: + app: vllm + instance: {{ $i }} + release: {{ $.Release.Name }} + template: + metadata: + labels: + app: vllm + instance: {{ $i }} + release: {{ $.Release.Name }} + spec: + containers: + - name: vllm + image: "{{ $.Values.vllm.image.repository }}:{{ $.Values.vllm.image.tag }}" + imagePullPolicy: {{ $.Values.vllm.image.pullPolicy }} + ports: + - name: http + containerPort: {{ $.Values.vllm.service.port }} + resources: + {{- toYaml $.Values.vllm.resources | nindent 12 }} + env: + - name: VLLM_ATTENTION_BACKEND + value: "FLASH_ATTN" + - name: VLLM_FLASH_ATTN_VERSION + value: "3" + - name: VLLM_USE_V1 + value: "1" + - name: CUDA_VISIBLE_DEVICES + value: "{{ $i }}" + command: {{ $.Values.vllm.command | default (list "--model" $.Values.vllm.model "--max-model-len" $.Values.vllm.maxModelLen) }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ $.Release.Name }}-vllm-{{ $i }} + labels: + app: vllm + instance: {{ $i }} + release: {{ $.Release.Name }} +spec: + type: {{ $.Values.vllm.service.type }} + ports: + - port: {{ $.Values.vllm.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: vllm + instance: {{ $i }} + release: {{ $.Release.Name }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/atoma-node/values-dev.yaml b/helm/atoma-node/values-dev.yaml new file mode 100644 index 00000000..e69de29b diff --git a/helm/atoma-node/values.yaml b/helm/atoma-node/values.yaml new file mode 100644 index 00000000..c15817e8 --- /dev/null +++ b/helm/atoma-node/values.yaml @@ -0,0 +1,179 @@ +# Global settings +global: + environment: production + domain: atoma.network + +# Main application settings +atomaNode: + image: + repository: ghcr.io/atoma-network/atoma-node + tag: latest + pullPolicy: IfNotPresent + replicas: 1 + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "8Gi" + cpu: "4" + service: + main: + port: 3000 + daemon: + port: 3001 + p2p: + port: 4001 + ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: node.atoma.network + paths: + - path: / + pathType: Prefix + service: main + - host: vllm.atoma.network + paths: + - path: / + pathType: Prefix + service: vllm + - host: sglang.atoma.network + paths: + - path: / + pathType: Prefix + service: sglang + persistence: + enabled: true + storageClass: "standard" + size: 10Gi + accessMode: ReadWriteOnce + +# VLLM settings +vllm: + enabled: true + replicas: 8 + image: + repository: vllm/vllm-openai + tag: v0.8.1 + pullPolicy: IfNotPresent + resources: + requests: + memory: "16Gi" + cpu: "4" + nvidia.com/gpu: 1 + limits: + memory: "32Gi" + cpu: "8" + nvidia.com/gpu: 1 + service: + port: 8000 + +# SGLang settings +sglang: + enabled: true + image: + repository: lmsysorg/sglang + tag: latest + pullPolicy: IfNotPresent + resources: + requests: + memory: "32Gi" + cpu: "8" + nvidia.com/gpu: 8 + limits: + memory: "64Gi" + cpu: "16" + nvidia.com/gpu: 8 + service: + port: 3000 + +# PostgreSQL settings +postgresql: + enabled: true + auth: + database: atoma_node + username: atoma_node + password: "" + primary: + persistence: + size: 10Gi + service: + port: 5432 + +# Monitoring stack settings +prometheus: + enabled: true + server: + persistentVolume: + size: 10Gi + alertmanager: + persistentVolume: + size: 2Gi + +grafana: + adminUser: admin + adminPassword: admin + enabled: true + persistence: + size: 10Gi + ingress: + enabled: true + hosts: + - grafana.atoma.network + +loki: + enabled: true + persistence: + size: 10Gi + ingress: + enabled: true + hosts: + - loki.atoma.network + +tempo: + enabled: true + persistence: + size: 10Gi + ingress: + enabled: true + hosts: + - tempo.atoma.network + +# OpenTelemetry Collector settings +otelCollector: + enabled: true + config: + receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + processors: + batch: {} + exporters: + prometheus: + endpoint: prometheus-server:9090 + loki: + endpoint: loki:3100 + otlp: + endpoint: tempo:4317 + service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlp] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheus] + logs: + receivers: [otlp] + processors: [batch] + exporters: [loki] diff --git a/helm/infrastructure/Chart.yaml b/helm/infrastructure/Chart.yaml new file mode 100644 index 00000000..ca58e447 --- /dev/null +++ b/helm/infrastructure/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: infrastructure +description: Infrastructure components for Atoma Node deployment +type: application +version: 0.1.0 +appVersion: "1.0.0" +dependencies: + - name: metallb + version: "0.13.10" + repository: https://metallb.github.io/metallb + condition: metallb.enabled + - name: nvidia-device-plugin + version: "0.14.0" + repository: https://nvidia.github.io/k8s-device-plugin + condition: nvidiaDevicePlugin.enabled + - name: ingress-nginx + version: "4.7.1" + repository: https://kubernetes.github.io/ingress-nginx + condition: ingressNginx.enabled + - name: cert-manager + version: "v1.13.3" + repository: https://charts.jetstack.io + condition: certManager.enabled diff --git a/helm/infrastructure/metallb-config.yaml b/helm/infrastructure/metallb-config.yaml new file mode 100644 index 00000000..0f93290f --- /dev/null +++ b/helm/infrastructure/metallb-config.yaml @@ -0,0 +1,32 @@ +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: traefik-pool + namespace: metallb-system +spec: + addresses: + - 10.0.235.50/32 +--- +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: prometheus-pool + namespace: metallb-system +spec: + addresses: + - 10.0.235.51/32 +--- +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: l2 + namespace: metallb-system +--- +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: grafana-pool + namespace: metallb-system +spec: + addresses: + - 10.0.235.52/32 diff --git a/helm/infrastructure/values.yaml b/helm/infrastructure/values.yaml new file mode 100644 index 00000000..3cce875d --- /dev/null +++ b/helm/infrastructure/values.yaml @@ -0,0 +1,51 @@ +# MetalLB Configuration +metallb: + enabled: true + configInline: + address-pools: + - name: default + protocol: layer2 + addresses: + - 192.168.1.240-192.168.1.250 # Adjust based on your network + +# NVIDIA Device Plugin Configuration +nvidiaDevicePlugin: + enabled: true + runtimeClassName: nvidia + resources: + requests: + cpu: "100m" + memory: "100Mi" + limits: + cpu: "200m" + memory: "200Mi" + +# Ingress NGINX Configuration +ingressNginx: + enabled: true + controller: + service: + type: LoadBalancer + resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "200m" + memory: "256Mi" + metrics: + enabled: true + serviceMonitor: + enabled: true + +# Cert-Manager Configuration +certManager: + enabled: true + installCRDs: true + resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "200m" + memory: "256Mi" diff --git a/scripts/cleanup-minikube.sh b/scripts/cleanup-minikube.sh new file mode 100644 index 00000000..b9816ea4 --- /dev/null +++ b/scripts/cleanup-minikube.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Exit on error +set -e + +echo "Cleaning up Minikube environment..." + +# Delete Atoma Node release +echo "Deleting Atoma Node release..." +helm uninstall atoma-node -n atoma || true + +# Delete infrastructure release +echo "Deleting infrastructure release..." +helm uninstall infrastructure -n infrastructure || true + +# Delete namespaces +echo "Deleting namespaces..." +kubectl delete namespace atoma || true +kubectl delete namespace infrastructure || true + +# Stop Minikube +echo "Stopping Minikube..." +minikube stop + +# Delete Minikube cluster +echo "Deleting Minikube cluster..." +minikube delete + +echo "Cleanup complete!" \ No newline at end of file diff --git a/scripts/setup-minikube.sh b/scripts/setup-minikube.sh new file mode 100644 index 00000000..fa6f1123 --- /dev/null +++ b/scripts/setup-minikube.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +# Exit on error +set -e + +# Check if minikube is installed +if ! command -v minikube &> /dev/null; then + echo "Minikube is not installed. Please install it first." + exit 1 +fi + +# Check if docker is installed +if ! command -v docker &> /dev/null; then + echo "Docker is not installed. Please install it first." + exit 1 +fi + +# Check if helm is installed +if ! command -v helm &> /dev/null; then + echo "Helm is not installed. Please install it first." + exit 1 +fi + +# Start minikube with GPU support +echo "Starting Minikube with GPU support..." +minikube start \ + --driver=docker \ + --container-runtime=containerd \ + --feature-gates=DevicePlugins=true \ + --addons=ingress \ + --cpus=8 \ + --memory=16384 \ + --gpus=1 + +# Enable GPU support +echo "Enabling GPU support..." +minikube ssh "sudo nvidia-smi" + +# Add required Helm repositories +echo "Adding Helm repositories..." +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo add grafana https://grafana.github.io/helm-charts +helm repo add metallb https://metallb.github.io/metallb +helm repo add nvidia https://nvidia.github.io/k8s-device-plugin +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm repo add jetstack https://charts.jetstack.io +helm repo update + +# Install infrastructure components +echo "Installing infrastructure components..." +helm dependency update ./helm/infrastructure +helm install infrastructure ./helm/infrastructure \ + --namespace infrastructure \ + --create-namespace \ + --wait + +# Configure MetalLB for Minikube +echo "Configuring MetalLB..." +kubectl apply -f - < values-local.yaml < Date: Thu, 5 Jun 2025 11:25:20 -0500 Subject: [PATCH 13/15] build: update values-dev.yaml --- helm/atoma-node/values-dev.yaml | 218 ++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/helm/atoma-node/values-dev.yaml b/helm/atoma-node/values-dev.yaml index e69de29b..3b254be2 100644 --- a/helm/atoma-node/values-dev.yaml +++ b/helm/atoma-node/values-dev.yaml @@ -0,0 +1,218 @@ +# Development environment settings +global: + environment: development + domain: atoma.network + +# Main application settings +atomaNode: + image: + repository: ghcr.io/atoma-network/atoma-node + tag: latest + pullPolicy: Always + replicas: 1 + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "8Gi" + cpu: "4" + service: + main: + port: 3000 + daemon: + port: 3001 + p2p: + port: 4001 + ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-staging" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: node-dev.atoma.network + paths: + - path: / + pathType: Prefix + service: main + - host: vllm-dev.atoma.network + paths: + - path: / + pathType: Prefix + service: vllm + - host: sglang-dev.atoma.network + paths: + - path: / + pathType: Prefix + service: sglang + persistence: + enabled: true + storageClass: "standard" + size: 10Gi + accessMode: ReadWriteOnce + config: + environment: "development" + logLevel: "debug" + extraEnv: + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://otel-collector:4317 + - name: RUST_LOG + value: "info" + +# VLLM settings +vllm: + enabled: true + replicas: 1 # Reduced for development + image: + repository: vllm/vllm-openai + tag: v0.8.1 + pullPolicy: Always + resources: + requests: + memory: "16Gi" + cpu: "4" + nvidia.com/gpu: 1 + limits: + memory: "32Gi" + cpu: "8" + nvidia.com/gpu: 1 + service: + port: 8000 + extraEnv: + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://otel-collector:4317 + - name: OTEL_SERVICE_NAME + value: vllm + - name: OTEL_LOGS_EXPORTER + value: otlp + +# SGLang settings +sglang: + enabled: true + image: + repository: lmsysorg/sglang + tag: latest + pullPolicy: Always + resources: + requests: + memory: "32Gi" + cpu: "8" + nvidia.com/gpu: 1 # Reduced for development + limits: + memory: "64Gi" + cpu: "16" + nvidia.com/gpu: 1 # Reduced for development + service: + port: 3000 + extraEnv: + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://otel-collector:4317 + - name: OTEL_SERVICE_NAME + value: sglang + - name: OTEL_LOGS_EXPORTER + value: otlp + +# PostgreSQL settings +postgresql: + enabled: true + auth: + database: atoma_node_dev + username: atoma_node_dev + password: "dev_password" # Change this in production + primary: + persistence: + size: 5Gi + service: + port: 5432 + +# Monitoring stack settings +prometheus: + enabled: true + server: + persistentVolume: + size: 5Gi + ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-staging" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - prometheus-dev.atoma.network + service: + type: LoadBalancer + servicePort: 9090 + targetPort: 9090 + +grafana: + enabled: true + persistence: + size: 5Gi + admin: + existingSecret: "grafana-admin" + userKey: admin-user + passwordKey: admin-password + security: + adminPassword: "admin123" # Change this in production + ingress: + enabled: true + hosts: + - grafana-dev.atoma.network + service: + type: LoadBalancer + port: 3000 + targetPort: 3000 + annotations: + metallb.universe.tf/address-pool: grafana-pool + +loki: + enabled: true + deploymentMode: SingleBinary + auth_enabled: false + config: | + auth_enabled: false + server: + http_listen_port: 3100 + common: + path_prefix: /var/loki + replication_factor: 1 + ring: + kvstore: + store: inmemory + schema_config: + configs: + - from: 2020-10-24 + store: boltdb-shipper + object_store: filesystem + schema: v11 + index: + prefix: index_ + period: 24h + storage_config: + boltdb_shipper: + active_index_directory: /var/loki/index + shared_store: filesystem + filesystem: + directory: /var/loki/chunks + singleBinary: + replicas: 1 + persistence: + enabled: true + size: 10Gi + storageClass: local-path + write: + replicas: 0 + read: + replicas: 0 + backend: + replicas: 0 + +tempo: + enabled: true + persistence: + size: 5Gi + ingress: + enabled: true + hosts: + - tempo-dev.atoma.network From 9ec3a509d2b6ad9c91fca087036d45c9f34b1e9d Mon Sep 17 00:00:00 2001 From: maschad Date: Fri, 6 Jun 2025 19:25:12 +0000 Subject: [PATCH 14/15] Update Helm deployment configuration for development environment - Replace PVC with emptyDir for data volume in atoma-node deployment - Standardize service port configurations in values-dev.yaml - Change storage class from 'standard' to 'gp2' for AWS compatibility - Add Chart.lock and charts directory from dependency updates - Add PVC template for future persistent storage needs --- helm/atoma-node/Chart.lock | 15 +++++++++++++ helm/atoma-node/charts/grafana-7.0.0.tgz | Bin 0 -> 40215 bytes helm/atoma-node/charts/loki-5.41.3.tgz | Bin 0 -> 187488 bytes helm/atoma-node/charts/prometheus-25.0.0.tgz | Bin 0 -> 71764 bytes helm/atoma-node/charts/tempo-1.5.0.tgz | Bin 0 -> 8437 bytes .../templates/atoma-node-deployment.yaml | 3 +-- helm/atoma-node/templates/pvc.yaml | 17 +++++++++++++++ helm/atoma-node/values-dev.yaml | 20 ++++++++---------- 8 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 helm/atoma-node/Chart.lock create mode 100644 helm/atoma-node/charts/grafana-7.0.0.tgz create mode 100644 helm/atoma-node/charts/loki-5.41.3.tgz create mode 100644 helm/atoma-node/charts/prometheus-25.0.0.tgz create mode 100644 helm/atoma-node/charts/tempo-1.5.0.tgz create mode 100644 helm/atoma-node/templates/pvc.yaml diff --git a/helm/atoma-node/Chart.lock b/helm/atoma-node/Chart.lock new file mode 100644 index 00000000..04c3290e --- /dev/null +++ b/helm/atoma-node/Chart.lock @@ -0,0 +1,15 @@ +dependencies: +- name: prometheus + repository: https://prometheus-community.github.io/helm-charts + version: 25.0.0 +- name: grafana + repository: https://grafana.github.io/helm-charts + version: 7.0.0 +- name: loki + repository: https://grafana.github.io/helm-charts + version: 5.41.3 +- name: tempo + repository: https://grafana.github.io/helm-charts + version: 1.5.0 +digest: sha256:9ccf73ebce95cc136f31ff45eca85232279cee14c36992a71b4d9052fac45572 +generated: "2025-06-06T14:57:32.451129313Z" diff --git a/helm/atoma-node/charts/grafana-7.0.0.tgz b/helm/atoma-node/charts/grafana-7.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1ec95bb75e66422245db9c1570e6c42604a411e3 GIT binary patch literal 40215 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 n9_>FKDc zVQyr3R8em|NM&qo0PMYMd)u~_FuK1U{R%uv=EUB|k|jHFQ}tw?*LBj?r%vKGPTF^7 z&mILLAqm?Q!4jY^O*6m!JXp9807;3GFX_el(8eIJuy9*gH>{PgbGmyp$GPZV<3;kf zyZ8+TgTcYmr||#5U=aR)xc~I4zYU*0+y83t)!^Cwv%d|7d&8&C{)Ps3f=%!%xWM_} z1~=|29NZt|hco&U$vI`|2wm)TaF*FW_xfM=_d7AkNE(wgqJ)n+J%kgA`3Nm&N?8Y? zh@~+VP#=o=B*l}2#2tiEydY)8oMens!Sd@7nhTNf(e7>}_Z{yqXxgW2w}a4y>HBGa z&>uc4yPM^Bic{RfGm?rP%Ses|%Y*(|D&f;~*5@RdzApdNtq*16K25p6N%FRYp%88C zq7HLixj&;~UQ8tRbCN82YC!Hl2K~MM@SqbD9_2Jsi2gv51%j$*%5wC*f`v}lIYl%@ zoG!A2>>wWDM9w8zu$Xk_Od{E?e;Ubd!nzS#?8YqOyKv?cEC?6737fGoPUEqhgnpLJ zItxrwfoV!|SZ_5sA}1FtznoK%l51P~FT$hwIVHTK{+56{oE3Sb_KL+oVJQ*N$N&y* z4F^ZYHHr=2D`V)0>x_&L&a#9?Sk7_B&i}Li(_w$V^QDj3@Al`=|Aa;7chbiS`agJj za8RND`%fR}|9$*I@_s8s`#uONoe?fZh|lrfvxCvI141UleSC26^(1;eJcx!*pY264 zc@`hUaU2~Crw7l!-k(0l)9HSUhwGU^~i7kEbGKM;C^dOh^^g5)`k2}fd1&`TDblN{w_M!CqZk!qJ*0|$eX zL^bEo+*BH&v`CUpExatJET`i7D8Zb+!3$ymWh|CpDiwTa5lg3ZR^)`EC@HugIf|KN zUC>rn7sv|@CtWnTMlqS0AcGO6sP=5m5fmL zyqJ()obonpd4?km;EnmyfZ*3$kVUssGg&|dpK#T^{Ezvp=VVEfh%K^0kQhyiB!Twk zA8WwS6P(})Njmzk6ZuCSjAOYGP*x-f;v~w6;IOoM1LPeX+S9ydlBNW-OtR9FzR(6#f{Y4B$cN#jilg$6DqECyPRB*Tp+<< zFCZolppr>aDnBGwQ}iY_VV|!fDC` z$Z6c}zL;bQyIzn~@ZB?k1(_Df83ESMsXjuVKDPj7t`HU=NZHN#x#wLwrHyZv26A zxz|tVf{GCu4qVu0Br4#tWvL)nN-g0Mf{evM6?HJD{QI00nFKc&4ArSB(!++*H!OX} zm>40E7o;ryz)4=#U5!)3jDAAtf7Bnf_(`(#HchT&x8Ko(s5{m{Ai^1*(1Z#Qe-!9%6(P&qr^aiRk!kYELV5O2z4!Y^O8*Em4GhiSfF#7#;_wN|00nc)3wtyaO2fGS390s&txr7%LIYE-eQ;67C=hBs^oB(@8>Pvw{=!50YNc zoTZW^p$nW-xi%chLlzG2pIfceR9>6iM+GN6r;ncPqes)8n3JAYe_Kt}nVf=p%p#o1 z9VX@^RSOYIWHI6-!&0)A(}=>5fjvfBPkj5F6yJtpP!Vs z2SAy}GjyI2c4lXRyq3ByatqSFaTqCp!u>|h_* zuAScWwHUVc22m+$x1ne%Io)zgnZLbFl_h9PZYeeE+vu?~t528p7cOfAT%fm_s%W%y zW!+{q3yOf|TZu_`?X8@GplPB4)b&ou)B00VKWT~NBl$S;4uw~E^XbcrR7!>cJV^mZ zXqSjcU9Y;9zAuA674=&(js;mnIGU5VAI*#Od<=gC$5LLIc8_kc9QEgn3)IJPoD*ldpzPfEpKY-M+EA+&GB+wW*^v9>pvAcl&moWIWPyF6 zuFAaiX*!Qy^9BOz5cnF%=1TVHtt6Hsv^Ut>>kWpz;m{qw+*KnqVTp(*JwB&dMsnx7 zV#3F|T6o`T^3WI8S+Ic=Y9n+p+;d9hc&^+%f>4%|DZNr=-Z6Y1``<}USv*2}Pv;G* z5KyGNNJw5%qF}Y;InyH$^60(BDV~wMpA*UZP?nBkl1L`fvJoq)EBwshq0)GvYMFsH z!uBS#B|3JNLloKo^O0?Lk(IP8C%i}`C3dOVF9pf3$2m^RTLLF^mX75IO88hp9CJZ3 zM?&6!2uiV9k~xip8g0#_I4G7IE%*}|HQV?CCp4zwT7e5o{VlzW)-9uU%jsPMU0O-= z+ETqn_{%7t`DqoZs-yq#dDXS~B26-aJKit79UiILx7w?cG{urg_*>W(-&OAsTVyyA z%=T*w?y5(e;^bP;$ZiQ+aF;Vo{h6hAH>2vhm|;OK@pZ{)JB4@E^E3xx+3tPw_g(il zzGV4%Iln>iUG*LtL95)mPSMSK6*-P*YL9)Rc(3dEWdD&-j!NNQ-g85J>aVj{1)`H2wL^;7y(q$=1 zXEeP+bCF@rFJq8hcGR+^ctPM&g-s`k}1oT zDwL*ECh-z;qE#n>uDIi?RAirHIW&@vm0@pxI8a8rS)N7yr>_TJKQkzUoq=oMom$Z_ zC%~|>Bp!3T$P&U`(?I3pc!96RQo=>UgXfCFlqHJSnC67fQ^L7qoPeRZ9--l2p{}ef zp~7-Glj||)zrN6O_-^%Agpx@70g_9f?ACXi}~2F_`4T4FO8u&v7VLVsk#Qy zzJD){HBVEg%(RsbRh>YwsWlQB#lBt#n$DDs^O7csBnoVSCBaCNYm~99kh25!vy?=H zbDUor&cujSMprM$B1_;r>K;0QvSd<3=SmgJJ!zef^#2XV3M_~EpW&b51x-ile~LbW zu9>UZ;rj+2-**hDl5EBqqp9!;b20;Kl2fGA--nSL;g96n`-V;FaqPFDvlNRWC)SbM z{kZ=f%Oi4%#r#ZMC#1Apsry)2O8inF0g=0f5!!qklif#re;lxutGDq4N9Sxh9b0+= z3+EMD*B`x^}PgNE3MTrua;Z0R`rv-=5iwB?Nzt0#-e70_2Sojo0f)f zkJ^o6nv+QR!&v&iTcLmr_283lt15C*O5zA}Ww=(GM9KMZ@Sz9bgtSUPee0u_lq=@3 z^x%UI76ljjltMFZ?BXE315ARWx$fec73dPU&V-&D^KQRW(YiVc+6s5;kwefBbi0;B z8%ID3ucwp_T}!1(Meg3S1G{e>we4`A@7Wra$n79uw?X&WV6d>XuBu$=4)^BWTi`og zeP<(mx0ixeLV^>FqgcHimI*HGDh{TW+UJ$S;2(uSZ?Nn$U@P)$K-kbMCRfO)I;w3eI*$d5$q31P*a_7Wb_tj9*Ra}W zre=l7uGKu;24Lk^Xol@-9>PJpDhOBjRy17e3Oa#yAs~FE=Yeu-)s4$yG$p|I@*R}W zh@_mTn_@x?9~pOQ$mt{Ni*-SADci9r`u^34Bu~mI(S3b#G3-C@Kdp>CbzDhgTkRvg zgP53;{8AzZVobpa>c1o48`FQIoXH=7x0~^?21tPRLS7rn%?dn8@es6PJ75G#}qcz_%6p|2N5c#NM7-o zBljtYE;I>UH|qcEQtTj>Lm5oikJh2cc=GtpI}#Ou=s-{oRqq+%P~Aeez3#xI$S>0b z&(OzSj}y@Y$^VJt24g^)W-t1?PA^YBg6;$U)_jJpH$vSvZ{HvOba-<7^5ppF)tj?d z_TD{H?rT!`K$mlpI&8)kq1pnDmy!lkeR<)!O_ki7 zy62mKsas6JDqRVWr{*;7=*^Q$#!^Ys+HzwrDcE+CQV&C7aBGYWk9aw!N?QX?kmdU9 zD3%eQ6T~H>Aw?H?pt!>GqY~IfUU2bIZz?R%M^_8^(X9`&`cZBkK=Ux80kyIkONTO9 z1rb+bpxx1qlLGMNYxrrI?nBF3)q3hZ%tl+}mnD|rF5mBNuc|YP`07d^Fn6s{&|Ork z74cnGT!jg;dKpdJn(Wz*@Xy~Is0%x&ySeY1 zPs*!0H1+w7^$UbX9$~SMF|22d{_N7DFZHmZoU2~KX1oEAz)H3A`+L6t`#`B*N??Zx z>;>>tPVE~u*n~n>)%0-3JpPzmtMVC1mF8bkb;($e9Q}(;)U2pBA>Iz#ZXc2Sr<}gH#le8Y{MPoDPX-d%h|HmeZ;<#^9 zssO_GF%3|vPkIb3VhPFJ_U5>lmZGfCIEo9F6bqsNoT>*bdd_}QUskk43s#s#sB8Zk zKKnr46{+(u1z`@#O7t07`MO~Y;qg}%WtWST$aq69{qB_pPBb_pE8`f4}1MZwv%1+__t&mYbb+5NS%Y? zl-?12c(a7!mA%Jmgp$(g(b+GG@||j{TO-NWCSl*a+()l)G}qr0e>@`std}^3`{gpH zg18slBPhn4vxovg1H}5;hOOkzIldqWm*#2gTsY=P`>&3K65fprs7SqU36~ercPvNb z3T(Ls89PF4L^sQXkO?B;Jjg6BN9XOd<}ZS~>b++|zccdaZM&OL$XYhx-GcfR{UMcV?ud)=3wifMJ^EzC3PKEOFdtxLt$3x0Eph9p9wT3CVbQ-}^#D6iGA}ToOwfjoX z9X{rPTiv{wR;Kfj|JaqnOH*}WV|3fJ+Aunz5)7wPl>S)3AYx9vi+>9%$J{c?THn))xz5 z(}Uo7!f&`T*Ry3Hfk9{9jjIT>W&=9CAHAHhO`uw8?_5{B557W zw6rWvCEFNKhKI~I<<7^ zU6IHWrUKZiMFT}|?jy2nsgAYL1}7vJehX8y?&>z+$8WmAYEm1ZFJJ0rV!ImX3iXHs zFMtB`#{)%={Bp7!;w)!HMS-bz6iq@a2k06+#gS&=%PKvfu;s!&Lcu;mpubYPW`zF5 zCL?$;YdkVz&AMujDLz&v%n>gZ<6KD%28tAfvwVbjvDlKIw%P#t$8hn_?QJxBlJ(S$@;a%tME z9*0yNSU8M^3ncf%juziMz+lETN_cEotAt=K&~W!DG_;kK%ENtV#8(Z#yxOHH`ktZK zv2ZM~&_Sz)&$v1#s!i3mzYcy*U>5De7)cAWEV%|_g?e0Nm}~%B>ho(NxR-pqjp?3*k6qu0rT46IwHe z)+bKuvOsiRFhR8CPtRZ9+9~9>pXDbQyQ^n+xt4u}V znn&5tk#u_;4qj-ji8Ki8fm~4OLF(qC*L<)jp_4eL8N*h$oE9qFd(IZi^18qw+!Z+?>W<8E z78jtG*|j%ll*fkX1<81-q}QLDO$o0Lr3KkFFB7bbu6C-(sH9{W%LRHqcs?*k21f1O zBI|IeJDkdU6P{kv^sL}>!qwd>mo1I;doFIG0#mLu@|w&jc}g&2t$3tJ9u-eG zx@7seq>cI!LUP?f(Dn%WT7K&WXIX71a0_MU$|qPTJit34W`=VC&RWxUXryl2jK!_B zc<_L*rzYehdWp$`rChs-uBpF+H?uxGgGm??y?k-%t!;px1Ih>@Ywaxuw`Nc)^ob2_ zwGO-|5DNpRT5D48T3hcg>FX5Dt_)ow+dyjVdD+xS)$j71QDhs*4GxG3!%}s`MkOok z&*jTEXUKWQ8}=Wk{K${Kpm;(S2MVUJVannLby9eHg)JLhfLGtC?l#JnIGYAS?Q$#M z7xpxB#^eI}i+GlO$YLoIa3Mv5+4U;ASgv{j4bOBt|0&QrmdHz1!8TLxmN}tY@zsYE zUtkKU8b@f@c~s+|EHN_9xbYU>?!X_TstH_2+Tnhyjt#-X)q84O2*x9vSb5N8TAhzR zn7)6B1zuyXX-XHx!fRfSj#kdl^qoa&&w+!l)`NquN;n73X;v9hj!r+ILePYAHJusB zBgrexNF6Zc`+ao!K{UKJ(_-NK}*udUHwXK_YxL&1uLlFSub1pHy~Eyy z&uw=IaRI46)aw0A)0kbBd5I37Vhab3;Cw@izC$8aga=h@+I;Mwx-@CauYmPBGObKk z<-M__GfBkP=tbb!$4+=lw7iKeHEfaVE#xazTPI6Uvx823)0O#wmNCcx!m!4*xq{pf z8-@j14#rgTV0kR|vWal`;Wb(QDuAko)cSAWoNFt10tdz2nt9S}diCB5`SI4=xVO>) zD}Eb{crAQ~3s)jGos#G}N`iZ&v=>8g09_|c_+)sr-Dy(r`IyfO0VF!h$(djonoH>t z5`RenhNQ6yvgE#EGJ#68lG+gYEN2nn9C4;(Uknj5&D$>RyzDC5L2h*~^irp&boG-$ zi_!KyzH$nt3;HWDypt3$1kMF0bd8=@d*14(5*H*#6QWTC$bhiisuBgI^j9INoX7xT zj($Eov3)Cr|9xDZ)(isVp6bTHkXGEdbSyxW)j0`Av>^`z>%lv zY**wssobvD!PVJEr7&S!j}oCBB<2O*F^~Ujs18Y&xUgez6BYe{ z^B9x?%Hx7f1^Shx@-IPqSR)`Wc>PZk7y~YEK@Ey!0Dt8V_!2A~I(89FXUDUY+2U7M zBr4?i-OLMc^I46A$3;RYo)gb+(fq1uR$!jaybIk=voaj`vD*d9Si)x45H-?wLZ34( zQWGYqs*LdtWvGd1(8G9bYLqZq1*1?xXBpT%oQDZjAO?R`*Qx3r-wxk%G-sa6zc) z5I6p+UJ)#NS`Z;AxXld)$_RO2KN|ROlR41L3lTbXL~_|e5yuj!jR?5N#Pv%WsLk{s z93p#I2=)?kQk?#DWK8pp4xp)OMKy3HFL5?4XHUqx_FO4~A+F(Y@V%2a)kgFMc3%+# zIU#*r#q|;~vF}RO|NnK1{@rP#L-2Ha-MVJo?v8dD(0l+#@pXz9G%BAMT9F&z!8{@` zlG`+^J1Aup;IIi0H4a>yh#eHo8M7`3W|z8e8MkKHo+Q18Kc7i-)gu~N@coakc2udP zc<_`+;OKl?VdnN-b?b5ta_wT+v#j$m*Q=r+R43Ew7~)j-jQj-_&<{xaW?~6`ZlQ|ANxo626A%kEZu>;ytJQC zZeAwIGy_^sqFDuJeK{mtlUjMCQOaw^r(W7?7HoV-D+HW20zs`jP{sE_E?6cPxYz9M ziw8*OXCwJWgS;ekHixv}1E*^_mwF!GlYHTiamlnw9nzP&=f52%qO7aQN2e}D z-Jp?u1d&i1GUdP9*fC|jo0~BulpUpTpW22t);-*XwX@`wd^_W#Rkpdoz}E9Egpca%gX+i;nI>Iu9WCU=zojl9P+o592;=~G z-)rku+kq@aB#pQG=!DF0B(LqMX8$YQ?m$E(2>VU-7R-wjiA$zy@f{s>2MlKNo9P<- zNE7Jc9l^1p_QB=9Orq3tx3M7UZtpvgAM3!cHn&2nmDbi};%24a^`y*(=IaZWmVsK! z#s%Hql>}(V_8|Bz6K>=*-H$!U-E1wT7q`i#)*u(gM#dl?!0*BqbSq{Q-#YYQMfr1C zQS`lTBw_t$8(~;iy}3NB0o{UQuLtQyVy+LVrKsENcajSlAA`?6GYy+{_wBE=qH|%;N=~x4~ zxquu1+E7Ne6SBFOTnFM#C1u@VD-YfB65Cj043_;4q{fCGLcwvl|7HNB*@JVL99f6S zdZJ`Ag!QFKmjhW#pfpH)3Aahz_aDu1(p|R)NqL#1tY_+egyrBkwUn2GR-1~<0ho=X z<_e78h2Z?)MEM~7{}rTviFZQKgsSjDEGAogyQ50Kr6y*?Doua~#nT52*3bvsT>KC5 z2y_6$HFW?r;9Nz({w6l>D$F}Gem8ZsLM5<{fa-6bdk|CC>hQ+G>KeV>L}c~5*-UWt zVQeJ6)}yhJ5bJ@uxhPu;ZbN}Kn5fOg+GaqvB;1DQH;{61l5}f)m3_3hMQda6wsu?G zfsnhjpHS3&CCi&-T^|+%I#HWui6`oE>A4oW&1C52fvzV>uY$9_JYBuV*OIDzgzm~- zTOIa;IJ!(6b(y*Qkx6SeW=qMmwryKK)Y`^XiSoWprW$e`o8AzM?Lo}?ql#G{I6;K! zr)CiJb*6Foz8v< zBluKC7j0d^Nu?4hEH;>hKj_$jO#Q$lLsvcK+FT*`@D9p+9to6@z$ZLUiNyI_2A(3b^7Ce#BAcS_a|q|`|oQcH^o9ECV490p^d*NhqJcVd5RZ@wLtyCpl-6*si`8O~O0+|0`ZD>srg-EW0W2 zmMHB^&#t;|7`)O+*lY$Sq$%3Fx`OL-|LO|;@c#X&n@Zt=FDvSfV|q4Fbiw_v%m zGWm*}osr~mGQoE*S$>`{97B<2J@{_f)$SwH&4Y*17w}Lj7s8J#S|3$(9d)rGU**2+rGvVPq)a)aG901dKvAx|NDphv-mvr?W5Us- zK=B7oa#M4CwCo?X$VtKJZJJ!me!rs$;n!S{g@ZwaGoa`XG7U zQ$w>}!HX?W3HooJTIq3J)^45P4Ry=AZrBC`>Dr*@R^7L;=MD8~bhQvF8==Y-)HO>lzKh~((_ z^sOW>pk=IqC@X)f>ng*kQ&7dR63wV#kZp21*R>1OaAvg~HL17Sj;gd#W|wTCZeGFI zitx4RP6Mg;SVK`fp5)>B==I@$$mU+|=pXHwHK~j^`u7)T*dIOzU=2Fl7Bj3{ww~dp zwso0Kmg%no7@O;{JWZC-A&smKy+*U8Wu#KeYFf&o5!&@AFbqU~fD^3JoE}SHHYbwa z9?=du&B>Hr?Vx@i5fSyb(SpwA0^x)+Z$&59DjB$4{Zd>G`ZgVpG;D{UG-~F8nZ2oo zPU-0E>{b+Sx;s$18N|I6QSFwj$0PJD<}?CjedRcU1>mp{^NOm8t8dVZq=H8E8n|W0 z?>+d8+rA49pC_)jF)B*I|vRMO;yG}*E9%;l8p{zPG@t_(8()CDT@q< z5&HLD$M8^cCi*`~)Og>W5XmNWTvjW`>5QPq0*ziYHn2eLlyBLi-91c9Qc3-OV2&^3 z0H4SyeIh5miyn)<{A;^aJ2Ts5TT`-K!JeeChagEfL7zW2;rgU`@t>&J?W$(iPDOKr z?G=E_VYCG7juOUFj!nA?Q7%c8d-!w$PqnN1=(U_jE$g8teKQ456iv|W&$R8)s!ohT zUmr=yYtmGb=`3fHI@(}soXQF#y$eP5J+YZK{hLLxhc*|olrJbwUXny|^+sp+OwnGI zLc1*PXd|X(!^`=2^ZoIg|AP_+eEUKt08iNLJNRpaelw?G0kdI@m6DG`JD5Ev9RD zW5`h1HEpiOQ=k35S0b8c!;cL&*MwMaHZz3(kyb z*W;1a0jyGBORzWMTZG6gF71fbIu_`fGh>PGJ1K5`c&AhpYNWF0L!N|JlWhW-nS(Y^ zwye>&`W1-ZOqbR))T_W$d*c-%Wzw8*mdMpKf(M)en=`zxH8-k~zqC4wxV#sE8rWoe zZ@?QMR5q-!g1)BVjOgo+_pk9njY@WEGzxXXJdENynh+(xYx#W-^*(>@q(oe@{9Nmd zn=OcMR6TTROel3SNJG56T63he#h7fxCY87KzNvJ%HBIn^ljYj=3$0_ z#6_xk${q0C0XAMa;>?{q&qlG)H(I0myn#N-w)J|5D;YiPxv3+~Oh!m=By@EA^4(@L zb*vdNIS-OytKi^F5il-->F%$UQ<7ZJgb0HQM{|;szQZ@Vy>g8mXXPU+xO{as(ul@Z zZMjTbZ@oEyp8w%?hQ)g6wg8usMHxWd>s6(8cdIBpk)3wsjmv;_qO~NF@?80Tdj701l;0R z>kjmBK~u^;qG(Q&SS2%U4G)^udToZzJ+Ea=BUo{?C-=wj0L3}IAo|Tsj=|Y9f>?p- z!zsx@IsA7EPu<$a-6LiZ?=NU-ojm}do(9#EpnAU)G&+aWS-n|7W5rvX(KI6HlBh7a za3u^)A3G|k)`H+vBC1g|&+3hTvxlGx^pi6tl;Mx0g5+6FIq4;A#`U*Tt6t$nMsg($ zb`Y8r(K#ud-INwb<>|5#nX=2LEVc2fi+o00jge`X7m3zuuh$O83!1iWS8--n{^8HS zJ-jskX|`9{42`rIWd#JwHp))rU^GI*z2~o~+@nfs1a?5Qz$u-A#qok-!@r`R4F{Rk zS>iwjE?Hp!^mg(u5((qY*YlF>Ukt%-@0_ObNS~0`ID@hU5je&I8>hdt)GV62r>EQ| zAL%#Kre;kY;JZQ8HJ&iDoE4elQCKdA)g&skAynlETnPPGSUO;N4D#)0jj!Z~N2~ zn_xj!p>qD5BMn6F>e377oLk}z!Il|e)zFW0<&pJEcaQrcy=MBr4+gKPE_I27)9zVKvuCE& z62A%u%|6&jeMbt%%6q0Xau7FGiUsF1Lm~k~mgH~Bi&Dx6l5C&|Ckb$lvhMqY|0HW( z2cqk%R6U)LSBX5gAZo*vo<0Gs-!!2SCVwTA!!{G9p-)P<5IvS}$<>dvc^ zGdeSTLNCbe^pR*tZWo#;+U;MBQLL>H*a8(yhttt311?mCpSAQKw*D^5ky63(AW)$H zbP7z6YPBW$r5Dz&@y&vT17z(PUf}#3)E8pjFRhg7dKt^AA$(=MShE-P>dEQgC*_mr z(v0s|5=)wq3Z~^c&@Fp#vIIxwZPyg)kC5|ZpnP$RsxOX9u@Oj`wIl6#iw!+-g7at& zCr*v8=M9jiT$yKxE9YB5?yX$=OR9}BZHXCVf{@L(jB!G&Q4yvIz?3Hh=t{lSeBa{Rv<+@AtZxIKNv`A$x6`CW} zg(J(@q1&yvqpch}@&Hbl+1ciz7(tn%QJrv>6oP;W!Yan+L~evArXQXIl^UU z>QNsURpiA#AtXgPNf?gJDk>o1tp6;>bOze?_sW7XGV|su=XVh1=OBJ5N|F#P1%@bL zdWS_UO^MRND(vLpG#f}$F0dmW3^<9tL5m`qgISQ|nwdojJtvSe-OM}M8qT+Ypic=_ z`)&%8NEQ2l; zV(1b}u0y-=6$OTD^FOxZ9)*rKP ztX3zo+6W!L`R;A!Zx6q}=no#(8Ak%X`{wQYS7&{3C2q5i!C){rc={CnKNt+c{}1;D zgTD=*KHL9l@YUei{x>cl4K<3eUT-%*2i-Ce{e87*bC_Y(`Qc) z9_asl{62l!{d?zCI%RnTt`#&^0=DAp`<*jFTD?qV7bL%+im z1<7%Oy8rlZ-23&g_n(8_*ZpztpIy{P-ClPa{f0zdq!BvUhkw$=Suvf`EBP@f7>5P@ zZa}g+YR;}Q2qYXlDFowFzhOMGVmXM%#<6Ae0)ZVfzee4_?9x>&OE13iyGyhsxQO9| z@QB!|vN!7{WIJ0n6LsTs14N){ktFbxrG9*F-S2eOZrx>N;8^rboyH2w&^C>|tMM_c z%IJmlYSkPC1j(tLA_q?`EpZK{|BfUCbD|nsT)on@5zKO$iYe;;8}I!a?*`CRzpY@V z@zQ^*RkS*BfNSV|2g2fl<}ngi=mqL4@2xAL5{4_z2)%Ir4ehsBrrwcmgIO?1SRwwC zr36raVM^2^u&_b>&M~@`H~tB6i{)DS?9FVTt$S$-0?@#P8Vzn5bQnCNADS$(yI~TbCwIG zCX**d0i#o;h%s7FjHWr^b4ZSllLXXkx}DvZT)#H?uP*+tuA4^xzq+zc5C2zpTLM<@ z^{VI+=b*puhm+O39Dco~11gFn?X+qakhe`QG!mh1KA(VfR z<5SjU?S3OKP9uSAn?@zO?-KgH^=m+bZM4bY+|Y?_hUN)M>>p$hD*AC9IL>p2Iu45g51B~n^ zwpir$)q;B3*0hgJSV|Z}?MALvs|X-9RenQ){RdtoP85KrK5JhGLl5$7HK=lhynlJO zhC`0!fz}#RNs56aYx=Hg0R?D0?6OL?Q5bP$C0|?n8~7OFh@kHO)3xIAIuhj(TP#?r zJ*^thOLWKuiQk+gi$0(40?MEq_7AHbiaxDj4Btu&hjKf}tG=RV-bP#A`q?1$aGm2U z3u0QjN^8ICiuR%bF#0W&QC)0)L%$SEkYFaOcqotSxHq|0`g21+ba?gJl{oX`#y6d( zUP_&^Q>Ve5mTaX<|(L zYJKf~)6*}#K7Fz)wpBaa?Iu8}J2s;Y2taoAKf0}eG>ZJ+!n0m2>8pf61x?_^SM2AC zn39~NQPYe~Fek?g;CD-TM!Oh}UZDOgVH2F#EEVo<)_B)_>v~eY%~l}+maS}YQ@g3F zJKHiuY$Qq8mW2}>G5wE{o(Xmaac#D@OA!AHsGxzM;*m)?2qg7Nd7v36=f@pdDX5gh zcK6@7mbz;i!1yYvqYy-U2#q_%^R~3F^TB%8K%Ln+)mx%eX3_{WnehLvo}n(Pl04K^ zJH~mLtagz8>aS0?#u8|!J4ha8_o+f=oxzrBRpq@~Kkl;Dnxl8LwwujC70$(-Oh&`N zsXttYP}b<9wFbmTs4{hZt0~btyV}OtX8k(7N}Gi>VL8jlZ}lV8_10Whx7E69+69Z7 z!-H+Kg5Q&rC_lx3cb9_)t8t{*Emhpa4MLr>m5j4*EtVDdzIrFRriam$oIdKrKecT&t$2pTW%p)$NUvy1Th-afrT&5-y_W}nyNHT zi2Rg}wfCcmMo{_S;_ol1zY%!GQciUAgAi)N=XS8oaL4?dW@C7qaBb18`PL3wZx<@e7)cbreV4OIgwm@ddsLA=r}k5ON}NO1KL`7qyy$ z;6`an(Pj~EL(*muAU0E>1r$*jTS;*}(gFV-Pm1Z3ceJ|7#`1vesamS!2U$=nW3`$d{A-buHWLIDK{%HG7%A!nkJ{`k^>Hby*3drr z$h0!~G>#f76?vjJv;Hfb5t{0!{Q8DOv zL&fRE&r^FmU{zzbz%(8Eb==ar{b9b-xC*J&?tlhf>m|*~P<66G=CQ0L$N;6)*i=Sd z8FZGjf00O7Am!iw_Y#C#;@1iAsT9}Nt^%&$sF+Zvrc=;JefoZxs#m!Y-CMKN3+;Q< z&p*pSp{tG>4tRIKDBrEH~Tru8Kg=o_s^N!JkNDj5Emq&hoyzbB#Y zc0{qw?%4HMV!bzHqs`f4XAS<`+_9c5)$p&wD?97^z$!nm$`7pa1FQVND#sO{8+|*# zBk%5V?o>G@DY~i|)Fo`G;YUAz?!e!WyU>kKS2f$zv9oU8kf$YU3l8MQr0h}{r|)g+ zM5-NznkqV7?5*+;#7R^n#*@OZ=@39f6CrdiSSRwpu0W1ovh zB1_QLg5)!B(t{U-XiFA)L(yo+sgkJ3v2KTS*B}=-QEit_J>EDWR=P^bB$&#|34750D9Hs782ZYda%s zxWsBuumLg@7LoZ=B9d;10zy=fg^lwDyi->}hR9%D6KV~jar!`Z8|p7{!YiJUHT#5XO`tG%PF{$ z+K_Z`)J%emCbt6pxoqY7^P6`z{UawCSpV?!Sl8wKA23H<9l3+1v;O&W<&rn-Zlf(u z78jB_E;5`G)K$4$zU~dW$OrgiF)^0EzV*5@Zds1EO+!x(!scvlfbfI}Y+PluC%fp;P*R`#dJQp($HcS#8$|1dG(&*=J){^ow5k5Src~4QZ^8M{)Q``ngUSuk` zahR-U71E~RanQ~kM8m_7%CP&+yP2jJH*1Vutyl_*;GK}WO9$?sF{+6_g>CqHV}8ymu_LCnpgx?fO?y_bB;hwZ&XZ$EmZ0$SKa z6HA{wYs}CK3&O^$loF|AcEU$(8O{aTqNEQYQg&2?laT|xJ=UaDvWKk*ZX~X3fSC~9SwPX31Fh2kTHq?{lUhqj8%!!VUo%6e%~)tf zP$gTNr_s^DW*q5!T02h;tMph;Zv7kcUc@Tk&u!C*m)z13y}XVs zzX|c+J0>)6S}Z0cAEEuB&R_28JsjX~mH{=p2O`Y_r1sXl@Jzvh_%dgz)e)(@=#Uqu zS(1R_SlvqLP2UZ-d3{I=wZ&_meg*HS>R*RIV~f+uC2*d-m?^XtK?R?tGnHdNQu^04 zJ$~E7wP|nMRtDC}c>4dwCDx>y@ggrCWXn=II2W>%6^Ky%o_*qVgV-;2}BX z`hmWRHe(&38}>`>OK#Jn=2%yl`L;2yQA`pVK_*Iz6R2198{h_PWPSJ2@!s>--!-k`q*;ja zrX9JFE%d&X{~OhuN*yH4Io;cJ8#z#)=s~^&ZG5@QO&GSueU5^+Lc#dA+^A|nOgGh7 z-&%5%O@ov)zAYtNf2>vYvP(Y!u~i4y**=z(t+9o4g0kxK)wvMaXm__}k=3s2qo(ur z#*N{$v~&G6h*V?Vk=MM7(GAA>4Yz%WR`=e~o5{2>T!=;2iJDSoX4Hu-(D$!SR7wm4 z)?fLlGNd5hR3GYtRoG~-b%UCLm36h_hcmh@=jMQVJ;0&qN<>Zaw&wFpf~pHDg>w=k z6HD9VPlHEXIGTHvR93&$gLc}eQL9dujlFD=H#KjV4X5_{v(akZBMhV@$_Y3G;DjTZ zuAT)y$mM&OyVaXOb#}IF{r%(Edh+FAT^);4t+CGbT0dzz1Ei%eDz70VD>Sd>O-AR2 zxNT($!Ox#Nmn=Ug`8dm2L^vlLp=aM9^vK3+9-(0ea(0XpHbZDPCKtP0#3awZ>0~rE zto^Q7WV-;lpV9am12%`CE#s5{e$R(r?|swhkPFDD_^E@Cu2y=UaD<)?zCH+180hY)Rqe;nuniyWS9qag96k_2!~PJy>f_6c z>M?l80yr6I`!?k)QrU?=)klK8Ao+xGa>8aa>#(HV=wM^IJ5%MX{ifp!nBWqS+3d8B z6E>TH`1}dv%K!QAf9&o56QP019&3*NQ2x2PGlb~$g$4PIFUfzd5$R7jTM3jwF zmKA(%iyFr6PM6JVs(laiUn?frX%$XO0}#b4#VIdCF7#`^mweXe&%m=)YlR$FzdAn^ z@1(j>I0GIc=ykYm7pn^(p3|nQFh)B^?lWb$x=A{0{eFlY0UhjnI^f)ijt{$pf zUNDR74F>4R+aGmuWDh4covPB$K+SFEPVuKQN?`j?Y%RKP5NJ^CytZsyKfUWW4#%&6eaI78;0UKy zU@65nExkG2qfJCKZwhqBEqk_;cEg@0HVJjxp55i%pyy?j#-)mWYHd_mbinw7nZawshhk2I$!|ir$mygIEU(aEoKeYXE_UIjH$+)6 zT9TF{LcELsbCcsUS8p!cQs&h0=ZWaCi z{vWhUbzi%;Ix7aM1!^3YzcIJ^BgjBiWEmJ5_6I|M3f`IA$S%~|n%~!;?mVGf{P%qpr+AY*tfmB(k){cqZ#j{q5+cYQb^tDuyEEm~177oBalNG( zJWgYB^}SBQQl*SBxzg9@ZOo(Y^M4m4zdnoRB(9}5+KBymyggPD8>&@k85a~`~(~1wn?%9y#8f~>e(n*?C)}x3X;Y<@F%TVbW5!T zrpfX_<(8GKj;K_W2^|0ieaL1xjtP1U8n8nB)mZzeOf}fmb-Qg&=^q7c?`GT_;pWSH zvyIbu_1z|l4%`iRJ-fkIsY#UF4s?Tf*FYKy~M$cV1Yuxvc zTCR9g&3FHf_EtYRGpOZq5vY{BI8V#*_SLGo+YP2}sr*UxHw zwwCJIR<2_OEq@)qbjGuXpZiPv{C}FJ**t(fU2vm(fGhLA?mr9i|9$mrIC#kacOO6S zFx88^D!p6f!dvA`Sd;p*<8>IdNClwly3yw=eI{;n|7x6ho1|-0myOg%iMNW0uWO&x zgvM`|g5YLIW;ta!71tbl5$tqUq98pSO3z7<1g@Qp#S>H&sjp?_4bYWSVjK_&nonPu>U~+@8$O?OlWAB z&k2rzlmkZ?Z`XV+IC*tNxuEIn9ZS@EmvfrNBcyXs$7(s9f9aRJUhHCQgs5WJ+1d~jo zl73QNcFvIr-&SxMlL+T3uAZxkgo070hj2!}&smZ25&B2B`%m-;b-NNH8p&g6-T)AE zczTRxvZ8iED)NXx8zm?%aK^hksH>e{y8rATbV2e7RGASWzg=Px&E-GfLBKz~CoBpd z^pAhm6XY+wSs#A4{##A|CvqF6vm0CkEBXI}eV_jC4fYS7KG6UB__e10b;SK!wKX=Q z@#~XSy%T?5_X96fj&q4j52-0S^YS$TmQACr%SUBh*zEjQ6J#0x(T<#$cVsHNuU@{N zoJdnE@9vFny3#MU3@^5%v&sc)F25z4$JID}=_B)}`Zdu1d4_M!_nY4e`oBMTdJypc z&-V7edZ7RJ@w*e{H!cL@5l(12yV!$QamynjIrVoh_PVRhD6RD5TNOox(~srCx19%> z0pg&hDQ`9(OONt3SA$qIF1b@g`UgqiSSoOG%Hl)ag5>oVP|@XgVxUE_f{2`|NYAahRTjY4WWn<54}vE8LOdmT zL{fogHoQX*sQ{QJxxTi&8}ZNyCIKt60Eo7*aqF833Xc*B(%m^YIp6RF$?=RhAN@2m zs3wL**~?+u)^0r6n&7f4r}gOc!)?xKlofxz34Kt+{sn#w{9nf6nDV^HB6)7-$^1x#1Q5*l4g#zZkC zr!0PH_wTo`e{aHD`GWZ4dP)4&STJobRB#qpMDr5|@Dk9WInwRoz_LppQhb3atjGur zAISV)`qx1JoBfA2&7|2`W8^nZBp;Qw(yzx$>CEh&4i*Yl{mCF@$TT0_bkH*8z7 zyo`2QcjEH7O`oz|t-I~%;3Ol|1^?KtrR0jQ*9zI=QwUh0|2;x)SSrN|2!q7uZAqt`ixUL2}7b5FvdD1v=m%;s9{$IuXtE-y9w)_?cr1jh;Cd?Tgs3i`h{-2W=1|NDcz2l{^> zzdsB8Z^z2peSFJ2eXGl(qMmXMfU2ciwN9Pe2cn z3T@Xj=I*Lxu3kEH>k?<7-0=?by3Savl5ETEe+tINS^zcm6o4X&u^?w6$AZkR)dJ)! zk@$UpPsX?>_Xbst8iP(Xzss6Rj-062+CNY(Sblv%7gTtN>tF%ZTBwqTu`Zk1Ub~1E z+hq_7J5wrHIY}cEz{fe)hlOA-A@tMfPe&^ns}!$C38oAF$|OqA$nX9gX7wc&7$Qx~ ziFsRzQWg_PYe+D^MuNe{z!BL&oS{pCE^#VUs-{bP&5;g7b^6m0Byd{bbLA7nG95Td z!l5MJfw{V*NrG~cT+3c_(wh2dRJs2-z`-pUM%vBeH<9 zRPQ><-(n6Kuuyl`%`3YNxbQjNdv?H!Md;a7rEnRGYdF^>)aZGmw^Tzn4mG&Vo)T}V zHe9Sq7qt`Q%O`g(L|o2ej~bf3^hF2+ zp=OZF+J9sMX*fK}?!)OXYgaB9)fay@!H>On=v`LzKaY`s38V;7d*kt5V~4}sSrCmG{Z zvN-7T=TT!dfoJIR=l`Xyl;&!x|89V8=!bjTXjS_236pa!YuHpDU}h`%5J-CAdMB65 z^!nBNAKt$7J|T1=LD%-8mL1+3zb0bN0z^x=Cx_p@I;jU+H){$m+=jt~tk{9j1imcA zWc<_N$%j{s!~CUSf~?&`?OK%`fA{v}<*Rq=&cT!=F{G&I)&hO^>g?@@cSoBI#rV3{ z4Wwp<)#?6_(q9Vl9Zd*`*G;4S@aFh`e|RG{-ZteA@p0A=qRU|Xhihr-SO%BwI|oWyg7P6ug)OIvhT*;FS_o~>mMKLEhrVTjy-&!IW z0K6?MbsUorK@;AjEp7(P?VuIN6MmaUVPh$ZA;>imRhq8XQf%Y(44Pq~d#Y-1EokXV|W3sqNYB+XGtKRJ8wc4>JnD^z=GN<-#yW~kXlJDt*$3g?zJC4oY< zl0$NpdbhT_IOQq;e_7WS-ARWji-|Fhl-1qR8|$%a`xPu>37cJ?Ws;^Hv6PD(Q*Wm< zR=F#T5^cKlaIl3p>Qx;}2Sz=y1K)+0G#{Z)KBL)S)n8%vty3*~baKgwaY4&A>NKC? z95094qP51F~MIil^IKC$4XNiJ8T2pz+on8yDe)<|I zk4bB=s%#Y>a5H;bgK4l8{c}DT-wEsN>U&lHU%GQ0Goj7_Mb`+uIZ&0lx`u9(`};ME zeWs;{-Vq!_+SIpcL{`tJJ7O+aj%Nf+!S2kt<)u-5inhovsM`Y%#m}F+-A|v)KF|?x zWWmZ4QOkSjbVva|rtzqE40Fdpe)I>269?oWt!j%mZPrP zJ>6O#ZuJe~0PmSd!cg?a1=Yr(+hGB%4|k8H$@OhHbvMQjHdh=;cQ4eTsZ(j1vqfFC``P_Z=!E-F zSt){v@ae9G+^_6QGlW_KQ!}nw%}Cq6mdGElEFZ|NL)dwr~o|Nd2&|MTho z(`OI*-~0I0I9n|BAm{~w*Dr>Kk~`XA$8A90B=4^?g1QP=7uhs8s=!O7qiMPE3UIf6 zefGF42{Bi9m;!NTjpVl)_hV^_xwR+549YouH5XNclc5;{-0>|f*lrOZn#a2VQ5&L zU```hFJS!9ZxSLd5)noNtZasUnM$)3=a(!xC%Jm>w;bPN^-$2J6OCyE$~O3aSI4#K zR>lC3QFoTG2~M2P@VEYILlg#Tc20DssS_%q3?qR5{JB%9q}7k=>x1LpU-#E=|0}t1fiueh+&~go#s5DWJPqT& z4j=MA-_OtS|7&u#K~FJt}Q0s&a%|GQs3|NGA#RMsv?gQ%Hb2waQWVhPM+S||qz3qUtmHIl`w@!pY({mowzt<47J+Gv8 z+U_O>t+VpC9ER5FY}{4lcyJO{cp`#mp<6F9~G3VKgI94);hZ@@wz|>%~2GLGql&%c2fd zn)Ty06SY>jo|SFyFq}_$z*faJTsG3xF~C=}a_*yLoJX^0az8leLuCHW1ntVSH*aa}9VE+!_Mzv=o)hpY|Br*OLjR9v`@;wS-+TFi5!zAbUF^Z@5HF$E z;pwr~0Q3G2n4_*!fA?a~OZ?YTQ)ukde$Sz{+3UvhZ*8tNAp#Fw>Y#MhKd_wsDmj|u zl*Na-1<8N+iff~d0d!T4#CK$>pw(&SkqzhMXy-S$`xZ2Pw@jDouB%g5H^x`(8<%xU zJ#;-6>9m@VIliEV+cJNRT4=q!D^ovb9kuONhk+&wmS2AmG@-u&TXRbCh@=9~tW4R{ zS!dsA#$DfFhK~=!Xe|I@c&S!ThpC|Oa2W~Jor9C}4PTHP&xrF;txDx)4MHZUUJl!~ zt}!I6HLXXdA8vC_qpbMzP3Wz2Jq`E230rUr-XAOP|EGh{{yY5Y;Nktx{rp<^e=ND( zO&E@_>SO)kFr&x(Of=^dHUOxuRhLfK06Qx(&;uWi>=ZuHSCEXgn_^Jx?eh*Y|O&Le()J~<=#52Bmp>`WJNzL2}gpqAKY;Qbj z;N^=%`B&Yj=0Z41sSJyGCBKdC6~6h3;~q3nqm%!t(>6;sr-_UO)iYgQ0<5dmpzqhN zFrz>_AE4k~0097NT@FFCr*My}FqN&a3KF5TSWHN+iD0m#8k69ayEg+h0hm{1=Wc_q zhHz>_LTrFqoh7k;yFcj99)A9>f&b4~98;bbnYN^uRf&s0@Fm!{s;{pu8}{_uYvw6Uix;_KP%t= z98~o`PY?DU=>L8Enxtii>H}3aq_@WIt*(Nm7o^*vxyBM)9sFez8vFR}Xzb$(jTJdf zXD>B|{kh06=a+Hi0Z};trb-po?~iaco_~-!?nVE*_jDM>|M=?Q>BIZ~d-*l_Z`8m4 zan3I6kKa9mgc`m2^$tG?6p!_azdFUy5>2C#*+Z>nd*Cgf6B*b0_D_E{9LXAwi>f^s zE>&1vQ$sJps-IRO6eR^0BtJe?_3fXo>aU{bGl?$9FQ|LMaQrP!a2k=k8cV>%XuElR z9T)}^zijDn>zaEwXKtv*io1IycH?rDkupA>h5_fpd3nQP&_-=KC#i1l`$3+r!nNA0 zqS^|b_I;seBMB$Gl)p4JzW-lv{~O)*tpvir8Y4p1-mgt|&o$rc*24Uq*f(!r*Svu}a~(V8U&Ma-I2`X%!{vrX%Z8J$ zty{8%60L0-!llNkFHIHyI=|rlmw)ymoZ`HU0eGX>e}8LX|M@Dk{|pbldis$6?_Pco z*-S0?itvAWTR7L}s_=iRK{b$??yg1fKhtM&JHh`}Ak-@KpFt^%>UT?_|C&&Aqvrv` zf~FGD5~1bT()B{>Em%qg%V|0TJ;RX$WJQ3zt_~Mi@@PkMoQwWpmg%BRV(|6CrD`36 z&rCoi0^V)J;H#ge=K49o@$W2P-xB&CW3pf=C*r2k;0pSG@YOJk|NqtAR}b|6UVarR zqIOSvKESTCZvq znv1SxE25UcY95D?kXnVRSr$>oWm!rQvLUWaz`g|jlXY=re1w-rmBIO}5|^Wg<2Zj& zWlyA#T;ssmLp|8@a%?Z8&~E=~stK8HUp(GAefx6!=J55a?OG3B8;w5|B+qinNpnn( zUcpKJ;_=ppvsdr7!*7D5I2An_H&w_m=MtU6|DU~g?{3>h_l4)*txthrv)|f1ku2Gk z-Fi)WmSekV_Be^R?R3_hq)UZJNJ32!3;_C4C-d3gMSxcjyvVvlc7U1HSR{x$9{liJ zFD{Br5n8X=_DCmo0iO(NnvhR{78ZBm4cQ-X2l9W)aaZ)@b?ex1%JtY-fAH}uB>sl#CEc>FfqA-mxa-GDe zRr5|ddx?)UF~7<<&b()2Tf+^}yq%Ii_fK?J($*rbNn6r($(SkY^-?U?*sp_DmmSEk z+To5W^g0>v!;6c@&e#`aH8S3!b;;-IvAkzp{6&aFf1LeS?6=-eXI@ zdrKoN%Y;45F?5fS3-1y)alL*n564qP0-nCsj#1)%Na;?k8>3l9D6MV=|2+?NTeC8c zJxg+kvoH=o#r;1P&Ce|huH*mqd%Yt6*Z6;4Nm}sz{{aeOU-#k*9MXsD&%l5a;jc759GBz@d3knKo)g)r`VeG&=9d+qV?*i~ zkR@GFL!1~jjVGEE`k(IpGz4UZB0NkJq=x?Q^!8Qz-(Ih4?0;91gi!s+#o?wGa&sKg zIiXx|O2+K9$2j!tvH*8(CW|a2>r97TU~lZAmslJjS{2bW#78HeUF)7Fpl(=<2yHO} zmQYLE+WK$S`ES-?*LLT>*}+b)+Z8Bg3;KBLy0_&AY+|X^-cZa@#v)}CXpR*{4InZx zI3`nw6B;aBUF7?j9s;7hi)b#(l)s2p$87y7k55gzP~ZBZ0no!69)ayP2&fE^E*dDB z8^?Xm%4?%Sbivf+8T;^eaA%i#9*)D@jdYjB@{2YMKUuo= zk}^l*Omo*vU&*A!rgHq}KsFf?NFA0!!SvZIP_PF7b$fdX|95+*XY7AhlLUE6r?o1Y zahmIGBlX`jzuEO`n;Px^G*DdcA#A`}{r}y4_5RoI8vfr(QWgI%Ll!jbzbfwEOp$?A zy7DX}9=!tRuao@D1f`$TSg%?fv>5BW1+o+4fsA;Xlfb)Toj)1ztkfF`c-Hv^yNGpO zAr34??d(P_dRH-VMQ@@Eg*g*@`vv1^%g!Zt8HfK)Qt3hdnxq4uj0R6zia$Itt720{ogk9 zzmEP>1Rd5+w6JEUD%t|-=$QEEfps;lfg|r>hmP`TT}^@wJl7EFOK4W_Lu*^;2~bY| z^Bkyol)!4}|88%m;QwoX&-njXNm5weVdrE;k=9u(1?^BYB^>=Lq|_o` zCdAaLx=|O2si2cw&_`~(zyt(bkOolQ0(>|eEVhykp)F)tghnx#I#)jmTl^HmN(tdP zaT~cgtZaNFM`{D;VJQrGIVtl-%BBey3`V?Ra0yAINh=^iKK9 zCrAx%7T6-{Zyq4}~)>tPkMVE8ApxmZ)$ z?Y8<&7Ey)VUtb&Dm{ga_@qc*uw&Me9nfSlA+uc+3f4e&d|F0x18UJS$#2$wKb6RLi z#Q#gd-(UM$5#xVb=;oC-L!-v`ti+r{wkRra_7(|y4=0?|Wc(8_4DPeZ*N~Ymwn;lt4>LhecLuNdwR73xzfP1bO7+FGtqsvnDM)jg->AM0bQ~;oVqTep z`9FKA{a?4gWAOh1$?S&L|3DJ$V;{NLLl+#5QQ}w0ruYEd14*ex9oQQuZ>2n5 zRX}Nj!62N4K^m=K;1g%y5eE%2{wSpiB8RVRB z&iZi@c5SZv%a zIA~8EeQk&4Ce4%-{1++w`yyJ-qN0FT9@sw;&=90mXHC{E!v7GMS#s_>& ziSr%wdVXr*@6zr%nAzkS(Yu8Ft(d70*Z<9Kiw4J3agrW*Mx_O<=zyxK@PVgOb}$xv zH0g_;{aji@$%exJ_lXmSEMJ@qpw9lIzrCaK|M&Kd{ohKGvZaLYBA@vudMgUKCbq77 zL&OuKiL-D|$Tjm2ivMuRUDEbKbZ|vf{LREI`2I9F6@{vU%X=+O%DL4ffy0=DZmst$CUV4ou_yj4^L0mk+e5TD*dPA z8i#uw(Io22+($sY{kKa0_qX>9{a;BE@n15t4r2YdVh|WLv{>e=G`3q^P?*d|#V}df zcGzS(C0=wi%+#0|Zfl#89wy>wD-|RO-LYmZjbZ*n+NyH0v~4clBNo1?}`1D+a)t zAa~kfldTCMS5|oGV_bTHaNS~t_6@@03AYY_)$3cy{b&g7D>53L;wk0_V6O$j1KpP) zftUp1KBUlfk&D?>K8P1V#A3^=lQ6ke?y+Y$Vk?z&XP?!I0dA@SJoAKVmPfN@D2<3G zYnJ8|!+|EH>w|z+sT9Y&rX%8Ubj$T!scw%_M0vk6oB~~Ne3?-fA|K?K`t@bR+|fn~ z)wu`Q!~7^Vb7U&FH4p0&W}|F#b1E94qfVY>nRG|8aB@uND)`p%lW+0`hWH(NPF{4; z23teyZLx{+w2f{9TY2_5_ggmwWg8~(3narGvwXwCv=uNaTB3|o7Vm(M%0~dShlFs( zDfHVu53R5w;fQo_%(DKiWn>ZGWOc@A`{S9mk-)D zVyrD0doP5hT08T}LH2JEjzMIpFNq@PR2LNH(3Qi<74nKLDbK1_rCB!#_%FU{=)^#V ztu`yE=}HQRubmEO$vS%8U?nIemCSB#VkDdO4GBEX@&u^qDTi)3g}-Hr(XA`pVXKOQ zN?MrG^km|d;-_J?XVP*^pC>K>I#cY)!D>>*mM$j?hBBY}vhjd<*ZCeI?^+%DCgW8Y zExY18v?+odwIvf=dm`r7`c*JQHg{(~0ka=I=)@bs2c3D}f!7|eAw$mrpCCEYX#fr| zf!7E?H{3sWV2F?xjff+&XGu`jkS;FUA&3EwaH*gyJ`vLoM%YCRa3UV1lu0b7PNA5~ z38H93D0&W9goPkQfOzg*+@278g=J9`LQama^EIJc%rphRB^(`y??%LR$qn|#ARNee zq#0ln61Wb4?hU+SfFd4H4?w^IL5l$6kOvId#6cZU)ov_DSs8+KfJ6i3ppGzQ9M~=l z7y?9gt57tdX~<51S=^ve;Hte`B&zv4;D~Y1W`*6}E*__!KWj*Cw9Y|0wW$%-lpQ&- z*?YAW&22R2V2nIOv7I-*EQg_vgw1CZjqq*cy)x50OVc<2+U*e~)3%E_qR@Sj>DmkH z*@g{ygN|>`P;|JlN=sH@$EMZfXo_DvO9%MiMLFw2{cKLOB5GFW^56yFzFVB*XEUuZ z<(QsrePu@Dfh#LN0CJ1SB^KHHHktK+Au2GIHS1FJA!9d$mai6Nc(jWJj|8#VK%a_} zl#bBm3a4d3+x{s`PU%km=Flfaj6=#}nDoy31!6a#iQlJOAgVkHn}xYsEUOr|aMtp2 z`WK)UL(4UupTl%Z98@oQQRHPG+IOaEUS@1F zeA@#DFMv&;i$a-^f#V#^xr|gOYWw~ z2<1b1fj)taJbg=-MF+Dus|7ln zz~VH>keuj3Q7m@DBRo%DTr`IE-8q8JrEtOxw0>iu@3WL)bey!KiJ^J^+s2rdTA^l6nNor}PfPQmRn=NvUmCxDm7NJ)0 zz&k^sTrtJ-ma8$o&nQdes|q+_HguI(Wzs!7wjs+Y^@&g7H-)+YV4OmZ#&?mGIdQ{s z-}_Etp(3ovF9JG72cVlidGEn%h{cMRb}baii`lQ3aYFARo0v>XqD4~76*Xjb*E2fM zqH#Y{ZE>SDGO;JfzGA_2OZqOz?YIq}VvmE)WmIhEC1mIf@(Hk_?BXo3Hbq<51a9~D zSTLQ}4bi4F^;yF)rIDBG3nlZLu>ich!f5-RaIoLhNk!UNn+uiI5}=)4)` zix0=Ty4b?VG$}S}b>Y0y*0BH6?q0(hDrV)RfpJNrInvHhb;Z9>GA&g8OU}>vybUO& zifX}G3Xu$SIekn2v*6;uhKrA zk07S1V08;Hx}e3@PWqZkE}f$h_At+!oAHx_0$S{pfy8=~eNmU=6{2A!j}~8>M;wiB z&g|2<5ni!UPJH5$@!f?V{yZXHcsItmrBRw?Da~;sWTvm)YVmx*{INaZIqMZML>2#l8rq6?{#a>z*_2_ zI7-a-PNO43l11MiF`eu%7RovSu#u6#^nomTQGIk&r`l= z)keM8t&&T4qkDPg+?eO`Z)8NPOgj0`#D|Lt{U=h5{C{V=tIB_N`^Nv{YSJ>~KhH9J z`0H)q0KYm^bSD4S^>!0MQa#^)6J+Gn`35O=D*4)5SwdQ#O*%l%&?+gL`jt@pS9mjX z&xmsveSsPUrq&9osy!oD%9Ub{rf7kbu0>k8LNB)`Z@r6@?w%2br7 zBCg^lj5Ek~WU-X2F4Lwg^Ya=}I7XS5s-rDE7?QOdHvL*7er!IbM`!OBIiR*5JiXC0 zIBy-24*&VYaWJESA5Q2naK>mpMW7n|*WKUQRrLRR-ECw4wUV@2{hu)p3*~whj7`wn zR6$n~a3YO0ahxp8mttwIQVHmx5Nv4*97T1L8Of?{|3@NeDcqYN=(vc@69u4V|8IBq zRr}9wuW$DMO48!@zd$F25*yeCJ!~T<3v`h?x@zZ@8vteXLQWf5{mi0A7Zu#lo=9Kv zHKZ7N`~YMIO-N*?h)+-;bduuTANj*M0G1H#Tg^2IW0jWXihmCohZ*z=i^a#3SwiE; z^z`*v3jjO*`vANp9%|(z_!W|SUlr6#gnU*OUAQO)*U0UCRZO2PG@DN&Q-{8PmOSRR zUpR*zxbzKfe@dzD{9i@&cuigG$!TxJ|-`M}IB(0YJGq>$mRt)qMjK3sdyI+nyQ;t6S#Caov z>xUDlQ9F-~H`Gk>>p+-2UDR7dh2w&yAfVeyOeqr07GWSX0@i=Cr9!n&A7|+ z8Bt{x<_1I4YebL8)Q1!S>*p7s*V*1_cP&Lw{m)>CqJ`Lr{@qj-aT1=@junV9OY|_{ z}3}6NjWOslQG0o6M&jBOg27w#s@$hlv z?%WY2gR|d`gs=7~ypnRH)bluk3?uqnbovInE}+Q03#+84Jwn``U~epR)xgaJdB8)+ zK~ARyfUqI}b^Jr$zg@Q>tqxczgFbL-L=w;AI$W87wp+p-$D zN^2sz8eO!ocf~9o4V7PUjez0P(5{}tlPPXXbm z0beKkW#KqdnUs~QC2#Gz_WJIs{ExV3p|*d2YoPzJ*V|R?|GK-z{%EPIkWAP2StmdXU;;k?_a*?0~@ts8Y}hFTUrVhzIEc; zxX6f+SRpX=37qu(->8kEZ+|my5k;f)@#AS32ZO`J&wqz67 zDL`Iu2XlhET;Gy~kv?bGK{liv(a}6iDuks1-vP@4*mJNQQa&Mtvk67u@a#1hhZ&W= zgAbMjcT?yy>p3V3V>L!Re0u}AJqiDCG2xS9yRGVf?e({f z{_jeXRv-5$W@5~?O*1TvIL; zP0Vju5Co{(|NY*+y8pYz|Nm-IRxK{8e^|*C+^phCwiRHI(ee-1mWHI1bNg09M&i}} z=M698YQ|Z_eaGVrd%7Uh}|)crg1YU`V?S_ zoMTG-T!rG&a(H^$qG1*98KRn689K#YaGTLOks00ujEhVyhHRTm{WD5NIAKJ6|HJn! z7Wlp^RL-HBa;22=GlI-^DrFPD9CG8`KS`52sLT7Le)#Vz=>@!&NRri_mLZ)iaw20eoL^uS` zdd2+x-N4&3tNF0x4^V0PW_c6ny|D%Mb@PM(w5O1+bVymVUc(U4cL?ECk-Hhqxjf3l z!Q-uYbWLO4dPx-|RV&6`K9#Xf%Rf*vx88)9gG*(c0+iL#@ebkTO3P22+Sa9?F8x6K z8rxozxFUTf>5&BwJRzDRK<$nL+*Ez;TwPt)?tO3Eu-2wLJxn8)oKbVRUxp%%ozAlK z*cdwEBTyZjw256JzI5pr16|$ABZ0O=i+msJ%A(#Dbej4R_A|p2*RVFH{{jz_I5K+H zvfavVq@|&mgqdnzwNU(?6ks-(O0vqg(;>}o;CY&A=`j?svW($-<(y5jo_=dv-ogL+ z&|93FE~o58C!kYV%Q>;6_%irK5`p*L*QA#Myn2oS&ZU-otN~@slR0kzNfWiPAPu{| z?I{sxnMBrH?SA2ck(%i1L^C+fqVA7#gx+A&cqfI8;AW^qGA9j=CE@pIR^);yew{(% z$fd5?FeuJa0p++l=&(h8s))q97o{?vCwxO{r~cPB*q?pZ&u)kS9SLnDfXXGur}5t~ z(Nf#kn?7hM|6kQMQnHRXI&^tH0oF~lX%Yjmzt$GI-Ky*ggo4qYt1^I-U&Pkn)41S8 zX~0#rr#`POfhM3vQd%o;Srq#a=ouMEOdBFqw_T$Iemw&eo^~%ukHwg^r zfc-M4{oX0u>I{KBeLZ~^r$E|8lVV_M46!ppwJm+XkK8Prs|#eztW{E>ycv)*dfYCPYlDJP*e;g5tOMsknf0LIp-ZS7cMQdCjJ#$HT2#y7Ee>*i zYaQXMC}An)a&w9>PE*bbrHy2g>x2=T)nC0;E+0v|0ZxQs$9%5#Im|$TY4nTR%h!`U zV)jiDvr++vAuJ-FVo;I$WIc>2F=myj><`lMRv#Kam_|(cIpl%WZIg=}+B5&khr%3?CK~YNQ(y>C<*V=V z8t*GV^#^3>G3Sp(KG4~!D*`I(lE{@fz^$dJM=kj?oL?eg^iB`*TXuVRh21# zOwVvE$Paai!1Uar&#_yedJ<7T&^uz?uk^=hqUPbrs5xq!mO09qk1x2N?M*BwaL}i7 zx}5~e6^yG8n5a?!O$_b|FnIqG2nF0+K7RK<6cwajOjga~rFW8yhzW33Qwp(`s7Cgi z4X_d+t~RV&%5zEO-Qm$mtOLrX-aXwD?%S-o*m`td%|?f5<^WF%Y`avi?Sz_a3d7j6 zYpYRee*=2{+K8*7i~FD>`MIb|+e03~0evscSF>TEEBgPjYcmd4#Z;AJ|DyPqnrI#u zDDueAZV{>kK0LzqsnMDud+5Qr9vnotTzP(+bWXz6VX0CyQBhZ2()iCYvu`pmD<(-H z+nJPkICfh&8b43Obag+^)yZE)OA7!|59k*@vbB~J^S`OsYyGymSO1F}T%diGw5ZT! z#+pWijAur{yPARp@K*|t&HmM;7fYM-Kz3nT6qZ1+VSo#BnnU)Rl8m}Cr}eZ z0YOt)c3Qb*w5Ux9eu2*utlq+>QPrr8eT zwo8Pn8pEuwe%4k_QxyKCJH&Nle*vi)rnqH0)28~Tw^EcNT(RM=RnVO@z}DB8h51}O zM5%)=T3Zb+y?`#%9#tl>5+Ovmh(n)Vu1O6&(1hW6^yAENT`Jrjc@8MR&G&8QJiK2` ze!&hNIyiVpj*hNo=F}B&8s)3#+NTy|eZ3!9gb`?mn}Ao-&(qaKVl?RE@q7U^mNbg4 z^VnpqeprID@a9?<7GR?#4^RBnamhXTTMa!iN0AChraO#HOc(7}8}?t@RzLIavv?Jv zlSV_3Xwo-&FT~k#-B+}t7jEPSp2Ej^it73z646-?70C&m<7-;qDyHDEepyl){EeM+GBl6~jg|@#H0q8O7%jYL zwcM5Y!ZULHj7sXafZh!@`%x)>w2vGIXXTDBvz z)Q#cq{LbTGs0m!W5l&9~@jSD!9|x-P-=)pR|KQstj=R3^ zqfr_dPP#Aq`wN(`HyH8J-L2;g#V!C2hx?BUCZ1}O7eu|znnLc7w7G8F5(Agw2DbJ7 zuY2NUT)n|F`Jayr`J-Sf=RFy=)v}nhrmr^_A2iwZVNr(cRhz>0gHuoO~y&5sCi*ePsm6Sv8@9g>!T!u9x zZd-?y<~wY1f7}H=AUJm*r`n+Foa#wB+H5B_mWfn67nc9Wz!=Q=XqpMp3)I@M=8DcK zAvU&!t!?0maw?sn!`U1ySeQliflC#I2h3B0s9yI+di=vh6YQSKMV4_?Vk$C<7ECuV z;j3#b0@F~cfjJ_*3ndVM_ff3`DcB-XVom+4-Yg;I+_J;-SSt^H6MD7pVAc!^6AvZ7 zGAFHAaMR^pAOLnXcuZ|D@Y{fQ-ch`o{*+RKm0`~Y9Il5&IbF`}*T$7fM8aM_zkzc0V^FX=$qL@1I8#JJUZ{^>;-5Z4Q?q(rJ{d*C@3R-?QHL8 z?DxI6y~KXHb4ZKOxdo*UVUtJb8S0@Cx1Zv5?0Ubwdk5g&!a&b|Q%+92yNJ>?XQp{1 zQ619>3>9c4Fae(sTN&HF>!(~;@g#|)7+L5Y~i)`{oOM91DIDQJRDFncGT*v+n! zA}u44MYz?7{otnIU#wmC6Rr) z+DZ?lg@-TnjfEDSWkO4#<%8B%u>1k)6XGM)qywWI6=QMdc3b_uKh*xkc8 zp|B&lwi}W8%b{XJ_*hX=vo#YH$aEu>!{VgS-!J*M^JH-8+?gcI#rb)={G^YXD)xii z!b+bB#Py}`D(DO7*Jd#bO#NZB^P{YT@%kIl+T4^iWy(^XsLaCIj?RH+y{fd4YoZn; zyGfdEr_EmF*Nq?VOW_tsLdpOUP-Oaj2K>fzyFpgwfw|Yu3VVRI9j-@zOq!sy|0o<- zq6P9Pc0ASpn46Ha94IYbaBzhTM7_({y0)P~Ua9$Tu}kAP)Qj(?H_juZ`hm{0fAVmr zbGxf{7tbS_(?}2Flnaew-P9!4sDt6@#F%{99 zZRbnLRt}4Zf8z(FA@MjZSK~(Ko~9w0GC3oP9FrH{kgv?2D15S6l8r>ni-~hq+ez9a z{o-4%M9+#!u!|$7#RN(bzWc!%9F{i-xuCGdBE!e`3z4_jNGw$Ex}R=H_5)lYv(+s=&UF zt-JvsowVZarvYMuf6mw!m!r1OV3VgUl99ky{Mx&XV6S4%H*RHb-`DkL5%@)T0Q99e zvt9&h=BcJo)O?8i`g?p!dSuIrcArLC#E6BoRJ=fs z6L%;Sq)#)gz-*Q;C=F!jVik zull8#rC01JYUX$b*dy---&Jkt_!$rvL8)1bZ=o;SPz<;9mgiu+$wZ}KT2K&y zn+HqZXx5Lf@>4a-m#70pab3Dvjex`F0-7anEI~}T#2KRk_Xri5B>-1A0rqLnVYDzd z4_vYz5sYj8V9(@qyT5Bq2$B2aDk4tg}o-0z~DS0>S0&4w;FKpo@w}ixr!qN`Z`#VCPPpIy1F{#!K3J-jq-;A8ec9 zRNJ(XsnwO$$HQ4ZIl6{MjVHlnIUb$sTNv~_@cWg7L1Kxxq?4&LRbOb%G0P+VAVQ&m>iL}x&ekQ28Y>@dZebvL z@>3>UYO>Aq#38eg(GgFhQFb!QbJvcZzx*CW^}J7kMM^97qchY?Jo>n85L$b}hkn9W zE3B_@HYm&<^^fWOtm>$ga;>Cc0+Zm-05uKg(7UC+&EIo^4u%>d;GeCYFb-AusmSv) z*$c)?Qom-rzul&a`UO#pm5>%~TC9JMFqCU@$W9HBS==NiP&le6f%5WmaKnouu87^Z{4{A(XWwBn7%c!KupxM938mEpI~r(}R57e8amI0cBi2_o0zx&U_G3QL z#R!HOPTB92oXSeS=`iVYlE}C_cD&FvDscFHD(KK?(La}d^UbVdbm9dHL=1(z@!T>5 z&&+1|jMVJzL{}*J?WR8Nd*Q?H5gsur5zZI&>{Cwvnq;Up)@j5o_?2ES-t;yDj$)T62Jug<2UGyF3nGB63DwCqdrst-V61hMI%qEbuznOzAJXk3xb)E6o zUY~~Qj}H$sadV2j;D6F9#>i2EkP#Ii>{j6lc*$3g06K`fe6T?!bT_l-rNMzpr@wR> zS!o!nFcYlNJbgLGn3)XXkctHwsNJ~n0-_6KW>b!pO~ADcr7t0y|HR8LGK=oJ2=}+* zD6A|UP$e$bIEJeO+c>ZDy+J0!yfrudM-XiNUxU1V-+{@+U&> zq*-!5zD^LNvg&1AlU@n{UG~m70j-^PeI9cCWP!!;@bwwgCd^L~N}SOlyd-I-cu={wsB)gM z*(Xt&;1m2#Ku6JWBA!!3ir>j`Y0v-Uapg4`{L{P9=y-9uI@DIDps>?(E|;rjxf1mS zgwijpf03;$w)>_TF~99i`Wai7-(iijcN0wEH;PQqb=!n_LBZG{opx2u&lg>7K%|Ar zu@V1&#%HaALdbui_veCF{6m2e-{LQiR>w!c=j+qkF;?-jI+Y&?YkZ(0o%D{hi2lV5aT1+;U(?3L+PE&1V* z*lEmydP)}qTU!GcnG2lRa5!-4H8)y6Mt6MUQU!$OAA~>9f#%VzkSV2cFouI8QzX40O3n5q z|1q1Ki<}GU)}X^#<48W#w>aG2TjL4X++%(EO(MJhCv^KW3>yNyy>bL$#t8#Xj^?2i z-g8F_Tc;mn?L@`@5r=xRr>7(?Afk|UWgc`Tz0c3}UD=w;$UAl5gJbcjq{1Ck)XDz5 zf;}ET29?-V3AmzrsRfS%lSv%g>N$|@#ySc=8a;gbVWb(P!fI|vUbdQv zOb( z44j<>v^D}?+}dJ+ZK);ufHC@QRl}G%RP<~O?$$J9Scc2zHF+-Z)p^7Ogelx2a;m;< zuEvdDQ3yFRWf&mdyURj~A$F~w`^yUwQXtu@ndG<$N@0GXhcTpk-I-7gF@`6TnT5QqjV^idIZK0Xb(B)vfHaa<8}|E#%V1&HZg1v zumAQf6h#`!NqIuOVFN^u!{#vdP#1>75a~s;c2ZH){Xv1R@zaQb?pKCfp(*=hn)n@r zjz30FeQL}hu~S|gS^SNYBrE(?Zan`j={n4n9E%BG?w?77Y-1w{Nrc__5C1-Ie?bpd z7Z#5bMpHH1v_v)KW=BvcGFk?P#FZ|WIiM>B@aa>4{Qu^Q5gt7Ppk9g!W(%2 z=U$i;XLs^+^K>H=nP!pV6?(-lE-?3tMwGkwya!U8k4dxJ0K#p8g#eB(4pGGqfK*!k7!l_n1u&(&iFE03Wd*+%MTM z9iBMW^uU;4YNQ+AAEN%QE}ZS$Jl|-+!o=?F>FL%-TY4~xGJ>A~j}WS7Z?iUtsqhFW zx390u{qw%K`P;wZmsgc1Kpnp={;ES-B-qD4-J+#yb)fkmMlL2v_ z{tqusuna;F*>auuM81g1HQHh}W!c^WOy1o7Y}`(nkZQ5hA5-zy24AvHnY?$~?l(o|aeNVKCf=D2Fq!z*!a zqX=&^MX;MF!hk^rDlbKF?1lSPGNiU^SJ`nwr)aI$TI&f1Jm?xrD8>CF?j>WNd1ZR7 zh@5qb^mzC6M1=w9udxi= z+1TOy1g4g(zXG-Pd&ofhSBVO!@+P1Cog__>SD{zFPtTT~;z?lV7GW{4BT01-m`=YG zG2?Z5kP%wfy)pq!W@l|wAk;)a*qZj9cMyN){uMPB`eMu5%bWJV~BGr{Tcb= zXAV@NYz2Qdubng6mPtb4Z${iEby$DCrNXlm53MocZ_#%kyQyl=ocOlIknFf2M5r&z z+8gEi;a&oLkZU@hkBo==dS_*(d)8uQyL;B9*<}}psaH0QCNx%SCgf0pe%lYixNemcZytIT5yJZ z1Jl<={utfQdS5mQiS9})7Z%f`$7Yc~O%y-_<}%szef0~dy}JBOy|-WT%3@^F9#QY zs5&WSGGV2#WS~@@`&gmxlT>T_x4K&VNf4#>gZh5rVP&o#8PHhnpb#tltWe3b zeMa!xI99EMJ}hS?wWxM|s~;3GJHqm4%FJ_>h1yr}%k%(Hx<+q-w{lz0^a#Q(dt+Nd+B@e`Vv&D2UKJy6`VAM$2nr z{b)6p<50R`TK-2Wt4aES{lqUXK$Kz${;;}4dic-JqbXveF#m{rp*Fw;u}@IJ!?$T@ zu+I-b)%h`ol|L-RbmNoq<(qT;{At31u~2+VtaT{#n!xS;-}QiAMC!*;>gOm7PSVe` zFp1Aqq4tM|FM-of^sPW913<$_?&d)~X4Z)Z1aBsk1fU#Fk+rwd^atEik3(JwR-RYD z6oS*=xndMy&l6+y5}o$f3xAtPA~PS>(D*}=PZK)4dk?!-87eB*@?~0U8Mj`(2Oj^Z zIbcz8Txc$nzjZ(G>bl|%==T0CSX`1(_DEaoPo-+QEkACiVB#8lxRb^@d-J_|&T3vu zx%59J!-Mta^(lSr+hH2_cYA(80J>AqYywSWz=VR1lc-2?@{tac613-FV-sGy@6JIq zNp1MkYY7yoDN;(2CR^@;Kr+ZiL!!s#cxDbhK7_7sfUozE{K-a6y#7g~D2jvOp=~J; z1f@5jvr+NXl6izhr9b*$ME(=2xwlGTc~CB^?9T!4NfwS37SUWTmVBnXwisVQ>kT_m z!EO*)7!z+M1JQq441-e!`yoJ_?B_X-2j7MV>wk`&yYsH3BX2D2aB&UXBH8>&mH2ilJErxlF-eOzL;?>p|Hk0aJ+tkvo$8KWUJ}6)0MOxvE=5V~}h6ugKX!lS4E5z%6 zP~4hCe`;=d)~l%?^tU@uxcQ(U35($Kap@08)cuXbb02W*-)o;o(*t<;+SR9_FKini zdFL%Xq&sjsH^Rx%S#;fV*K-Y@$!1_!A94~~UebtWmMsuLuAo?ld22ri)NRpA*z5sv zNV2_cAN@2Cz<$8VF@d-i0#S?Z2TlTw;=nKA`d5O!lGL$-ji4U(*;gD|Ix@KY9V|9d z(nzJL43DV-|L4(k^-|QN7%9G2g7I*caZxcda7+33U%21%5Nryg7z)rVX6vVJe(z@; zK?=hpTZm-GP zVCjA!%Fv9C)X^7H_r+D^UEfPI|+|!AHJfOHOf7FI!-Mpqr|i<>G${#MCCH3BREnZn&NV=q&*J- zU2IIYVy%Oq)#^>C;u+3eI^$#Q-`O!9W^5DsI4p;pOVt+S&spsC#h#oBo-k?YCoBo_ zsI?!TMXU1gP1GF~L=M^QjE}gqzpuq%t_O|VKJ;30gg<967)?A0)o0%F^-Y35; zcRX*g8N{g4Jl3N7}-ii?9BSijU1T1fR;2^lK@JdDbUod6^juQ%zO`)aAr@JK8 zeFk}dynf3Vkro+kNHXrdb2gx`G-{@4EgJ<+p0^@A7`1agKQi$&%UWtDfxihlDKuyZ zh$l%}MTW^ye25b$xAo#AA10xXsT0U8q|A2(hVZ^Ds2z_;U1&X?NXZ5)VguhS1!Nu0 z)2I)d_L;4l7#=Bwv<*004v1PLynZ0*pzBXSIk8p>_8W1m&VNbh4poEK>m_O_#~LK@ zo=2w}Em5|F28OedDu1;1vD<^|U^pbNL*tTe^UkLR#I(dNvbju95iB@M&4R3AF*@epk?KYPTMRZ4-4L+5Ch7MRike;UwW|*yvCULH^N}?#q{liA2DyQWwT=Oxi)TYgw zEG7Wk%&W9y2&u~T%Cxy^j&=l7Qu zeZn*deM*OM#^lu4GbVDz%v<&2H~{%2tUd~hSXdfthyHp_72uYbq3wmHit3WAPLHJ! z?N*H(;791jD;YQXt`>B*S%AZx+sozPb=6QDxU?=O$%x&@>C_j(_;tsj zQKZ>_nqyFxe1(uL+ghA+>7VnF?279R=Vk8G*2LZvvAZAbmiUHJ>sNC6hWA+Mx?7Qr zH`Ulyvfc02t@cF&zs)Tg%ueku?v3Z`yKA@KoTzR!(vnzfoW}GUiK9B6GfW8Y@58Zs zQqCsh=Gr5%B0~7bqXoyhYq5nc>gx18C|Rnk#s`#Zucs2AVNe)vjLi#@fK}~`7k*@& zw}tP}d-yL)fvy|hj-FJ1i$TRnXnf+%t9-wmOBX?Hvz;_Q*bV;Rjx7Hm;| z4j7$9j9??gFot8#Y8r-jKHR8X2sf+?u|h>6Yw*Ql-m#vmmXywD))~#? z&XThX-_1j#^7o)p%GkSIJ0G6w)Kj&%5jJOv%8+2{O&<3Jggp5_Vlsq%(KtpQQ)jp> zr>`oVe2@Fe#VgA@`!qu~C0%A)zp{|kW(;u$EMk=C_I-JjLzPpFO?j#{5g@YCJu6Sz zWZ*SKJe6HLNqkYLDqQZ-Rl^~zio17pS>wB%_|LvS$fMmqL#~V9TzdBtL}u#kyJ6D8 zz4iWTc9`1z$R10Bhb>lyPaLH08`ZBh*UMrtug-$Hc&^9e5HwZXoD}%t%s?9H>H3~j zj$7PR#lE2@k56cNK!b6jgd35?uqa(zai=$(Y{s{w@MnI8|Ckzx-4 zE_Xixk{l*>?-CeEM8U6bs2nGS1As>ibN4BY!$PEotzhuLMd}ivu*$TKl?f|#Hot{= zHNPEK!M@14-S3c-tp1=L>7>Z8>Ty9QJ_h)B*4J>&cn#|l?^OU*DqI;g>;JmA;mSXm zu6XhSv8b9J?YfVXwJur#_&L*nNy?@-*9+@&wuq@Mpro~V9DLq_CAF#7As%9Co0Q>W z#PHqs)nNIW3wQAe1ii@KA2YJ2x$ZP* z((&!>Dz3N@WmE&dXoUK+_8=9ju9Q z>3^_tmZmQ*bWyiif=@!r{cZDz+;h4@ZTAhb444Kb=Pf4`c_5XNf<@H3Si}|VWB-`=*UygMi*l#?A`FkULEG3em|~B7LkH4gcrs?AF<~4d%;@6l&^2jr zU=0AR%^1oN43bw2(2Wxb!sqohw^|z_jBr%t4^-saKCUiZTi4sywXba(SG2(Au8*&; zz9+X2zK^XhJO4kxHaq*ezP1mpc|BFCD&>Ce690=?z4U(_Qc1>>CL(j>1fx32?icgb z-iL-r;h`UC9M33Bx{E(nk68CGDWGe)1LGscXdK}O^HfDsIi(Y)Mcw2gZxZko{cna^ z*JQ9dk0QM0A&nBxvEpancS?S$sZ3&u0!m8&on2;oPS{*R&Lh#|QhO@AlAopUJ!plA zq7re*x!Uc0l zu@=!t)U!Dn(b$+7)3#X$sz7)xe&2{TNW**%PKbgNll3ues2Z#!tNfh+bE`g5KJ1Ee zK=F$MKWBD#`9}?*zAqJ}5@gk`!>1})7jC!JZRJ3fOc_P&h(Hg?BqjA& zt3@fzu`p@M6s23_Hp?e^pVefp42HEt)H8KPE;07jmGn~Fzd!b!QX057h;kCqom3E1* zFh;OG@dMC$Xa!h|e{| zP71TE13R!V9x|*4z{J=qjX^9(u#{kz`Wl9QMHQ%EX0RA)Bg_A`*6?O(??c8{B`mnp z>BJ6!Pn6&Sg|-QVH_iV4PFqT6&#UNv^v zf83~FX)E6EQ{1SKqmid%xe-KCwLI{4;<E8H{zun;h_Z#h zzs1HYP*+0RADD0a8<+u@-n{erB6jx@dhYuAy2@>4KVWvzLK1Ubq;$2cqL9OEEVZgo zF6FOaQKm~IjU20^cBzNRu-Y<-%4I7$I@dz4fsAr3<|uEqd&0S6q@0&l9qH1Xdz9$I@#83>~xNdYGi z@@BMVp0SN5{Zw)7eca>j6I&Rb-49~V9f~>1J5p-bVU;`?%T5_qurVIqaieB0ZFuW) zUO!5T)wvNTN0>RlGG5clNNs0A7|&bF(VA1DHP1unIE+(e0%c06OLZfP+^SU_iu7*f ze}K2J2J-$EjwL9N1p7sS`r9)9ea%~|afY61GpGZmU4>MN%!k?u(0K?ytMn$T8Aq6v8z>W*E)U{Pz7# zvqJ70o}aX7oltjm@!vSm*Y8Z}Q}Qv#R$`pbvTn}TnO&~nZ=Ie!WZze_YH!Xzx8NNF zKkuklhEO{dc8W(k2eDJk=TY#mS3{4nbPC>@K|4jXR*3^R99nu3@SPRx-bjYq(}37O zd#K(T9-k$g2Bsw7bMb(^^eUpCcjv48UY}>gcZ6j>&Epz|6pCG@TszhD?*mACUZ@J( zk}_}Gge(y&MeGX9+#d{R6ad$#B*=V5Nv&MbUsz8$^otI*eOVARk7H)1Gh#D&-O-FrLT;^MHOs zN8NGd@~7`VxT&ooGT7#9iQStExV}sbxZ;NZ9@IenO$_D5P=|fn0mtSjYxmwdS6tojYlU z!&p13beA3TUUf!2>K?|1j^)yj==>3NG@%`UdCI--vk_>b0j`;~a9TnuPD2wZo4Mk; zxCJnCcfane=c0|YOI2Uq;oIEs<#AU-!h<^K>~w2=agX=ULVe(Xc*yE$0Hg17qGWgl z6YMhO&$^!zHnd3;`FvUc%Qpy!Uui;hli(8;d(?*!RWk+iaV7z##0>BpLYhy1;usW{ z1O77mZADtd6i@L1A-uF8*e_!5@prFy4>y-GkcB{aZ`ADI1Ebs2AjfFB1;PmOMnGg@ z>6IdH2XXljvE^YO!K70v(famTjJ%}?Bj@5!o6JJo5bxRx=Y=Q^xFT3!G2rowgb#^4 zMC<$fGpw_cDu& z%MDYahg_aPnz73MgCJ$Q5cp~b$LY?d*qso94`krLr?p>EHmwg zY7Lshj@6;B5i_;9>W!lYm%8XtEoUtE1Z6{tF*bU3u#COtfB>r^DPh$)a?s z>HK|U4A9*$<?wV;tjbC$rA+5K8ZGB+rOfLU{euctgqz|3 zC;-_9)9MYm;i8(hrvm;?*y{b$E)`YQKhtBgfA~P(l>a)XfHH0**Ch7u*d9Q;f*tVk z-{494MEUaI9>;7bMlVC*+w<3*uPZx$K4PGE*XqVi_Ulg7PqtxTEss+esN0*hBJ%b_ zdXpP|caZ~x0L(vIL<*@q%5sl)Ua&v!>1&kRO4WOkV`A+KzDTX!Jtu};JN`&sof|Sf zmjq=dAOInU)1smdnxCe-W)@Y^e>8QCI!DCxKn%yPCJNm(e4o>HOhD#`uk4&)R(({X zX;R>yecyG89n)rwCJ0H3JE<`Cx$g_cxCX)m0=K!3U&B?yk?acieufr+&*l_*jQ-wT zcj_h6A=HX$ub1@qf3x&`yR~61NGLvhUFMxsN<&&~$kH~}F$KN;7FN?~mB=gWmUA>^><1cRP3|z4E$ppnpm3SdGG8cQ;aS@P(rDOv&~qH6gsXWu7~h((!SZyEctMIpvWY|$P|oFp9(cHX&#`R}^E5_{ zN*pu6Q%#E|qpDx%x;Kwb6I-oa15Ja+^_P9!=y!GH)9Z_c#9X97m%O2~(U6IQcA0WE zQReaO#gA!7w~Ejba&zO|dY+Bn*y>e`=3Qqyhna<-|KWz>W!8))YGwlI1W_gU<82!%dof0e`%8` zN_xOtg}bWD*LHsEg3?6ud|><3GOJG98R7i5Rc{=pHJi8|^`fR5$|HLHH%}8^^2Ky0 zXO>KwF05~)6EdlQ%iUVGl`Yhg4^k)>c7tD8bkDt*XNcZQF0$?PO(h(GR9IA`D_#XP zTX(PoZSB#Rr4_m6NHHW2TU=+(>T4Yos6zUN)d`99M5P<^gI+Nd1QXmOrC?2mW0Ri%5VwVy*#;jnq?CY$rE4w@G4qJQYIkLUo(7k>pCwsar{alovQXa*Z12I;{BEzhd#?#UfC za6orZeC}RB;k-x%JDLT?!yO#k?qXJ77`ky7zA~u>nG>V?Kiy&wn7Ba>ufHvWo zj7|Qshhh`@zF)^PS}jmlVlvvAjP}x$JxC)|@M_x_BbLC1IF1Hdw&xliL$#mXMEt_z zt8)Cg5aa3}=jF*X_VcAL9F7a`WNPbyd7aqqd3vbGo@WcJ<_|b01*p#sL5lDT2IrS% z=r0Wa8a0|(pA=w|Lp^Dl}JNE7s;c`C)Ga=93*FHU1&6#4DygtQpa9BRBq$xlD8Wc+L+G z2ENRFO^YPFm&rS%x(3guI$T?f5aq3onbk@8w>jGW6Ie?&vkP>GLM~+E1wou!`JTHc6SbM~bK-04(S1e75;#Wu($%#g{w8%>Tm+Xhq{^zjjE>-j!=n?q$KBtb)o%XXZkb zQ!5<8QMkJPqtGV7&Q5ewh^f50F4GdP0h2nC;5(I^j(Vp}jcZ_@a^<=yc!|P zx2!e9ExgX|@gAA3L|b$`0VW1a15sUdA{pUjX_7DNuu<`4c8@xL$>Z;c`+|4j*tVTaY}>Xqv2Al=+qN;WF|lpior#^incw}t z>#lph+%Mg|`mEDy@6&tNQ%~)xr$XZi+eN#bwG+u7=zHrHdHI`^xX6*6QWDRxC3@nr zRW4H=t_lKAjkS2bmG8cFTz&2jyVFSe2hfL=hkh`dqAAUl0WtAAfIy2Ib(ROpfyAYO z{d`M;(|TRDfaVA0OUqdb>PY^P{f;K5 z=7;d-wkYb|d~WmahbzNt%U=`00uJ%^lZMqlNj77IH4KEW-$&|sVF!0}iJ&+wFGUZR zJx@oE>YsFZh~G)JF9ru9tse{5Svk`ZH193Xrf2T;A1l{i=66NSFLN||oM>xp4e3LT zj*TD;TLl6!C4=ioUn(x&EEEs-=F{kxMAzjVl+wFBk=venM2CI5O}`PUfke4=zqvs~S#mX~EjjIVWL6oypdvUoE%cQq%F= z=RT13O3pZ}(#zyo!KBstf7Is4k3J0EQJ9O<+DlIC&Yo@$>3nmr)4fcZGwo{I(o%50 zYF(OzG2>_$V?2DR_i?O(r}W`oXVC6#i~Oe!h6?;2)U9WGg}xv@IE25d2ET;6RsDhg zg0hbh-~AOrh_1tm!KL*Bs%|9W#fC@YP~_#Hg^2XWRooQ3lmP!NlLJKyW?ZFNCH&zK zbK{<|o({Y_M+5q=hBW(G1GgLC9RGB&JNQT(j4;@MrD#IxlQZ~d7W5oUB5Wp!kO^bj z)5QW#Ij!WW6M={=TM=R!30xT&*~Vc%NKXSl+bqDWRt-PkEXkT+Vyz>>6Djiv;#uS)T{-c0T@oZzApSqgx?cTk`Fe zNqD`eEo+xIvuLOa%RPMU)G`+qQH1yBD?4Emnwi@gns>B7J?L`BY51BSeC(o z0y7>I-DZ&0lzxOhf+iz+0XZP8^6Cw5@bq0k%j4738TB2=%c2b15AdCEaS69;)|T%S z{q(jok2T{*tks9)FGIRnhrL%c;&H?Sy9)L#mdE=xeVRm5H)T4b`#5Q;(8HY8y(C4? zs?Di(ei+q$QBflBF_DWLkiN0K(s@+_L@aPTv0+T`+qFHCFBRB3k)!HBm1;(}eiEF9 zgZlWFDZS?LuM$={-TFnpy2oD+2h0qbZ`9Kqn6%FQ%$U`XoGWYT3nKCvPHO``Cnnj; z?;iq$H9Zcl&b=Sx_L9WK=vjo_HF4XS5BaO(Lm?a}M|^T`akM?XP&RL0=GR*-8{)N} z{XMq)Vj=RMsdzWVNgJ>HC%8IJW!*-d=u&{{=q(YY#hf}wW9wYAE0^o>AQksSt_+0o>{*QyMqGK;VbtTIE$i?3t_(ba>f1DeM) z6%EkZNBSt2$Jd+kIT5oyR|ANOuLqw*C)xc-=Y3zcG%SMAVELOt_lUi-9cTf(uykR z$Y54UKS0I~URQk_^l|LZ-3#d}5UMsf1(>?Du}~o65y4I(qt>G9?-}V~_tZ74(#(n* z%gJYd?-f}f=|2lL8gJWFNm{`8POsmt>)%J<#0O>hPl zjMR7;P$~?(wTlzyEHkY1%0j4Db2=uM^rr8XLCAd+WEKs5wVQsuSw>JpXsX3Jv138Y z?gb}kqO-_OsJhQn(JIL-?M5b;kQ+w_k9!)^GKKJ2wZkA()FvLw+)2rQ+{Ng*(=^;V z>>I|}7X%oHcgrOkiMI^U;;jw>SjKQQ_1!#K$f=D*Ir6-I$)-~UPj9G_6dXmmfK*%- zW#KLg>-U~3=cD{8NUF%kKBu;Li{upkp(e*mQb7HhkXS)POjY4TjRH$vwpQ^da((Dx zluCa>qD^8(w!~tF%``W2F^B4!ags<~StuqA9p%R#(qd6zbz2xxNg0h%LBgzEoIG7V zp^IFyDE<>YNf6(pPONP2O|ly2$hyL+nh;uz#E(uj@NLPonz=k>okg2UV1A}bOIf=- znI^@gf`X^H99N#&d`xSO_u;wXE({Ie+Ud_Ev&Ch^;LlBJvLfkI1{NO^PH%Gt!i?S& z0eD31Zvj&-K~k%OGF>uNn&XOY7hN}Q>5|EknSO5OIhmwYS&gl@{n%-nue~SnhGXcYbrE;5>R){`N+Ff03StWNr%#Y0g0IQVzc=JjATbwe|cTw z*K`IaZ?DNv9>S^Vc@d5M+C-vck*PfoAu}kPcSRU1^Ce%MGAh~@+rz+_TQ{MV%F)`T z*Tyh!pq9N}quvnmSln8zIlY6;3e6I0pjO?PQr5k3_Tp2;R%`jl?}LP0qR*w)_j*ER zUXtHwMVA1lkyO%Fo2m2bQx%<89`!cPmONiO_5Ca;`Hyfq`5>5@d~B7M#h!Lc-wzy2 z)JcD*A8R)f_exCDLZRnpRGx12?PHh_-J3V{>KZVRtM^f6t@{u`C zah=XAIQRC$G#B;!>=KFc`%g;O9l=FG*>=KVgsx9V(P;TmASx4*v>BJmTWbS|!P-K# zEJC%ZK!03_v0ELyd7W+|LCOns)A8=C)$$dmu#EMu)3LWRgg=7nKgXE$4wY z4^()Hri<}*8WD=zG53A)aC<3WsUSI&#>lA59g*ygffgmExH|Z1aQ76VYsL1x&EnBk zXf3?AmoQqZzsdkEw^CmFx?WHgA(0@CKBME2Acp<|U_zI4V<=PEg2d0w$~yx~4IFC< zQ5(gw78&?3)Fg*0JQFA>g`Je+`j0#IqY3Ycu!-y2k4sXsxN0gzPPdqTKoMo2gB*iR zT1I3fT`>}H2E{MrU!MY<`d+>bHol2mwizmR&-5k-6~BlkJJ3lp{ZI?<0k#psP-r1~ zRca;j@{K9{`zS~dz*{XK3vp}E7QlN59>9x6D9_e;xy%7OBS{b)Tl()h(6_e0Ezy(T zJ*W}#$*qNRfb$U58y(lx0A02DlW%K7?XuyUv$ z-~MbV1@B{z2_grO0EuY=M-si^DT0Y8%g7;xQIMzB$MI>6i%DiCjrM&1v_nxJA+*3Nhc{w*fs-o?{V`)-kCwCh{`GQK|zE>u=| zvwLr7;1d_H0d&BkOVj@hQvnQ21;h83YSevHFxnFW0tAkX6d2BA3k!-`F@EqF?G8y! z@Ks%qv5uW@s?oWJ-l-baG0XEP?x2S-VOi-E z)#X>aNEMJ^6K|%jJE7d^6RmyIaDn^us|bh5L1#gX`yVVs(-!*?bn}tSvkn(tq2`QQ z1+5t0VN1d9giw*ZRi!0$uE6P^WjRI|#}@N1y>Xwo4}ij-)`&-Kp89pI)Pk-q_eEBd zAr>H3&n3S|h>(HB1j)c7fs)WDK^iz0S>EIsq$pZzg1wXw6Z?*wt-3Km@-bKRI8g7hqAL@2ls*5RiNO% z<@venH`)|v`R;e$E}frx+pEX#MFQhuF$5&70z(7<=@7Zt-< z+dQwI9iv8lNR&q7y=d=t)|}RLic@b4A^b^vL&xp^__vhrxDuXIR(t(If?}FJX38w^{HA$BIqLK+g0If6i_cgc@&`N z%IsT`PnKqv@AJP61o_;&j$(Ks{Yyz?1Ia}i2NBB=*~kH7ar*R=`Ot!Zv~SDwF;;g> zwH=(Yhi&tx*HpbSToJ0;VQ+vPmdn7L)xgF zEaHiNO$DJI(2<@g{vF-(E*kzFT(AQIzBAQhj?&P((R7aJMWJEreb-T6H=)yr>!;!g zn)+Gh$x}WM`JFFKh5&^vjP|i`rc8}#ZJ?>p3U6;Mm$Nbbj&G5`c1H)moNi2f<-^xu zOaFMZ=evHcGxD_Lq6cUl%n9|F|w z8|>0hT8v~2oZpds(d6Lw0)(1-K4DB0t8i=Cl&e7NJOuR|H2}hI;vtVBVxgJT5ZT12EErF#0%3mh$8%EY{HwNr9ezVQ%}r)868j`@Y5|4ROI$Wg;LB2YSls)BLbZqukS~768GfT$ ztm=I;N{9Aumc6)*PKHMWdw2tl_|-ukG&3_M77rt6>AQB%pdRA1*ih8i9^%7Dv8$sK z3TO}5hP9gRK&5ZwV9QP`x!k1$b?iPPpTw$PHiC z`I7D4$(;FT3d0fsSCIQz=%a>4~ zKy2vU;fp^pBT8W5pV!2XZ3l@lk5QO9?La=L%)jf|D2_H8nI9#wU%FnJFj>Z+_l&J* z*60os>Co)|R$&I%%kQVJi+cpDGw3z-_^D|nWWkBq*-cQJOO&O=G)mjDsIy$KYFt#* zFuNBe6Ld7Lim!PG$24%b7j16qTU0zSTr>X2b&b)}FM4Y56NCnpPJhtg2tXNjs0l_X zjmNP7j}%6%Mt|O`Z36jr8`XiDKs=RZVN8mbwP_e>GBQSPvyYjnqMk9vQL=&;H`7pd zFIH6D$Xd1Pg$?*DrYow+47kRe4Y)4iP6CgxYEulj{yjhzw%(Ns1HVcg4V63j z?7L-f73bHzhNN+9`OKUc)l3dm&P4q0RH+^xq@n<&M3LN zg|>Rj;j^`jotR#!Ro;T&T5een;kaNI!b3NQgs%X>#`TQSRDcl}8tFwH2+&aYo;LK5 zBr%)8xmW?D*||GotTm9MCuKxzXXA_D}u3P>Zs*Gm)!=vGk^~AQzje7(j&? z0?4FFqg5e*Q&R`tC#v| zAlInTXt|vID)E3gDBy)K=>I`QkK{i9{$0lgG}!y>@Y?R`KKy=7-+;mRHEG@V?rhZe zP6&KA@;G=ie<*6#>_HB2`;>2chh+tBXVnlytw+0p$qY=frJh9_1?R2wpG6a$5mKrD z%*J8`4~1m~_XM6rLnG8v%k}p!%ly>|n+B2jYtlNOYBVCSbT`2oW}8cP&*X2dK0JCK z2oBupYtHg{ChA3TWq`KCOrUn1Km?eTZCil$33z}uCZYUR+4e>+jg{>rqoQ^GvS5uy zi1y4l{xMgy6)>)=%I`q{oe4G=)x;%)`ptMiED*ky|1P}CpaU@e7%UJz6@PXI_Ve`V zXl{GGpv1q+z}yXO_z{BQ1Rnk*ipURm<)g1bcKIZsu;r3^K;@D*d-qon?&dh{okjWq zQ6_qz^ogjT^bP#FCXalIeR2dI5@`P!=dsr!hh2(L*fuFWpf;)RyWo}FN96Q?IHXJh zvSb+`9Z+{EVApPdFAw`$Q-Hoj)B^LW1yCUUjM~(H<*`ybXK(V-)Q^2^b~>z@yzc!vHJ~b^u~hwVIj7If$>AvsdUJQ*EXI1ROUDUWirQ&l8tKE1 zd8sf~m~fAA#fLh+cLM6yW&fqca5y|_1dG7(m(h^?fyn|9@J5J$a53wocboXGuI37v^HR=6l zWD(*KN=ev>lg2jk$?j7|{QXadrb$9*C`bEPh zHzur$JUb$tXGoSK9W_W@1AS?u_SLlZIN-O;2wyGM-NQyaG;li;G#`PJiX=RvJFUw~dMESjZgsjhB%`}F z6eEWLK&@{R<3W>?*lp(ze+!-;{O$&s8ley2IgD6XhcIv`X~13_r~?Qbhc3{-9SG3C zLD)jji|E@+=P2LkKwu_Sd!z z$mawc$Y%txEU&$ngY=o*52lYJz#eEv2Dqnecwqe3?T!eF?vpPsN$a|w4)iFzVRb5- z!xFRVbi;6l_OFh$pnApf7X_ueQ_V%*S^j+A3k8ROM{N3hU(djYSO~STuV~1ld~R<< zvt$2Zm0+S_)IpE&s9Kz}U@V0vaqct{#jAtx!LDHR-s8RWmGWU?3TF~9ri{Q4TzdhG z9%$N42VmG4SRhyi{$%=i@w%aQ%eJ`!6bsJTzg?8Gf4|qo!e1>es6n0BS~kNUNcLP` zcVV{n0j2w^KO$uR+=H%(Ax%)F`a^Yfn7$grD z`$xT?L7=CSLEt|_9rz$WXv#BTnB|3Mwpvno%f``HEA}(!j13uNfrgSZx|I+%1rj{$ zd&PoFe6QH8Z;MZ{B`_)8;m?Q(BFPaC$fk@3e2**%@OlXvT|3uI(FGP=`*PAJwM^&> z`s15T^-KN7rd;^8fP-kTbXNl30{hY1^tFN7^pzmm@?J9Mb|b%_K<5eDTbQV1J_gO`NeJQ^1~<==nv&4PQ%VhEEiO0ZB(av+oME?3=7K-l{1 z%=|pCz(F=HHP8azZ=YNaNe>eLIe5|zN#8JYj%}hJVT#c0b_N93h|G8T;AHvBl>NhD zZEQC>buE{x2c=%fAn5y{1pRv`K@YNxpyy8Bv*yMlPg3=GkChT8xj%=`{oyFqT`S!g z7rsYT3a%FZJu2dle3&^I>&@wt39Djr1@xPxtzEeW3UlL!* zj7O_qI?e+X((^Gd1P~anX`7OOzp^gTo(rf8TleLb|783COPL1 zdADD4WUx`7zQaPNi-^^bxy?}=tWc}4BTOfWauOU3+o@E~PYKg=W<1j$$8wCfh%dNe zF+_hBig5;Risz1urCi>!WAD$>2}wD5Nz2In_)Zs}Gx!+`p>PM~94#;7q08FHUNx!* zGo86SUdk)AIP|Vd1>Tl12hv2eb`p#lAXP!jPW>%7Q=6wPHH=KS5+fr~ z3bIs#zIKpW!C!yzS@@ROzRTv_NSNH8-9{smfb-!2u_Pkq%$<7_%I z+(+ipu97RfNH>mJzQrP{_X?A0@ruMHK%?dX$+(PStT=~>-Z`EGi8vZ!&TPvE9tn4gK8f3gD+^B zq|_rRcIkS{#x@y**8gEUc+5r9t-V7!I`0##B4UJ zr&*4bWYljKApC^4-k!|KgomX%@<+@d!?DJy+9pe2iRj@xg z$v(dOlapgtN;^FK9whCn!E&e$13F@+>IKM`HxeF-xNX3ehIdcO%>6kSpwrQ?#oCkQ z6hXXW7`XJC2^WzP!DboxNgKGI-5OkUDyaGcw`jX*P`cLcyZP_VR?3%K=hkI!H(>hi z>SX7^?a3|IJ!ZZR_-LUOzaWIRA9*P!N-8?Z{5$! z6v}bug+EHX$`c+w%&GQbCo(j=PAl|dcI#Oi_jtO64+F_^&GYG&KCD}c?Iz13ORdx$ z5y-R8n-kAcHd{(^w|jhWO=B&i!p< zYl~WoFH_d;mF3LoB7@%dG9{;{%g0TQwe4G;t~;V|K$9;PKMu@y0`ZM_fD~ne<%p6c zsk2GwPCzT_7$b7Q=S7R&dOugemYA~QfP;iHoRBlRNtfFw3q|x_^mW_k)w!497JVUE zzlx6;bs`Att5RaxgpsbXmAI&XvCOU6QnmYbI>Gv$wFvZw-Y?@{|Drv7e@onYZdmPOe2BR~Nt51R=nZvuh8Y`CmkIBysU zEr9} zA|)ts%oOSa{eJ~Ul(&X{SND$?-|yviNL`7-YiDD9)UMO+Z3RI+v*?zQiW-1Y z)il*122xnVn?Unt%Um1G4)vLY4a_PT{64!%#<@-#lAp>6OiawAgE~*=Ec$)Px#9iui~5Tz zJg|~bde@$p9VI{j$Ug`UYm4>;76|-8n}qsEQuNCH(5}9 z?&~29vij1Yp9K7G9Jz|>fU`U1F-enm_$=ZX zs>PFoQGq*PM*+9dhSyNGO>S!HVK%gGhld}TWL@DOn!iRHrWZBREdVBE3lYM-e8Z zCJPZMiMaa0Un3_F@9ZIQv1T+}$W8!5hh0oigWbgoaB~@J4qC;W9m{N0B>VEoMX%Gu zn)o~-YY&T$0ZPXvl>+fO$r1G86`>Vjoi-oGqFcnQ<5kZA{29jl8E*dAyKmVdpPnpy zgG#->csq+LAD@4^GLsbC_9MoI<3tcOO`LVO}*lxHe#_sks z{rSvpyS-)k8+H4{-bRhK1)A;e3cwPW?Wc_pp6+|e4>lFLUmj)L_jsPvW(K|KfkwG4 z?oq9`&hmGjd(-k9Dj3GbpF$<=)tQ%%b4dU&G}ZJT(nf8&_^m?(RpuTXq~z z*MM!ix~HG^?)E31PoD1f=GUEe->S2nU-;+SuM=#w@s4;tE8P#Rw;?(SG@06&t9O(m zbLlK|Fd`!qEdw|Cg5{Z{aeqNzRjg7PHEmJr%AF$kiU|B@{~+o$#jU1+z0#u!p8O1Ah(-|d-ak++H+5bZ{0FL! zjO51ciA5AN?q-1&5cdroe+Zo}l7vL%k2N&L>rR#~2;KCO&lg*oSMNCLTA$*ihuoT0 zKeLJeS#}kxcK36H{;OMDwCEdAgDviHZmlvyl1QS{QcJ&Rn`r-Nb`U{~r8JFY^p-_C z4NDj#1?^6M@0jMX8d(SPrhd+2;tf&UJig$Do3uv%u`A6)8HmHDct$;_Z~cRWRcTod zmOSRR5D*-vGY5*^NZ&I5xrNdW`+Ww1{G>lSiI?R5+|_6g#OG-3=oZOZ?`3u zY5hK!K9w1w#>#2~F5ZV>gaa2e1K{G{Blu}HE*lJbHU;4fZ6-4Nw{lH4xcK)+*Fjd= z`OE)8)nr(ouUHj=HnP(hTkITjEQ_Q(6JRrHw%-{B_xs?4MaFD`_^oJebO?1bv1rsh z06k1@K+$EJ_tqK@;3Dj-jjtgnH$qRlQ>VcJ#CLx6$C2Pl5uU4ArD{dzFRL8wnhCCL z{h#rIM5EjXU5mku2rwn?>8LZz9-RToV`L9l$^tK16c-eD(N(~GKk%Ihkakz74_>183Z^KmRdLF)9||> zk{LbVv4Z=oi98X&?9ZU#9lDy3Jm5DQR|2@0VfyU<;!uuPQ@`u^)dh0Zw`i{u!r9nc zdEeY-aIOpzApRt|`%}{~2&2_;V(p0`Pf)*RYHh7@TS*0TFd;YV{dO>)|wRe_2|L6=H~KexFjS0d9B@=G4_tnvl`~;$pnGJj zDR%cspd5SMDx?I7&Agw~SMd08Y!dN`j0cK?nHYPj_ajjcX&P57EWO;tjS zfTJfu8jD?BW+%cnuceUz(VqiWQj#Ml_BjivEbYa=<`Uz=WoCIVG(3u!wE4 zHV>v*N-fl>sal_M>0Vcmn&S-mNIuFgjs|#23e}g09^TxHoA^s?)7#2oLA%xzge~Ie(g9fJ!jxcZaG^Knx<#-QG0`k2x8xWO8S){+xV<92W?# zWeWY(5nWS&8ZQINV&5lST>4lMIYzvWt;tU#6CwL^1<;)Du1V1yL?s*}_JhC0LmzNp z@WB}`tM9Va6FbcZx8;ZA<=el7HG(SO#F4Yc>*oAJDG_VL3A;Q!TZ#R4DUup?+i-ZU zz=}DT54U@ypX-&&iq)m^yK=XRPqUVYIr<=x3644<i1Hmh=^4mNjXWz~{Mt$~~ln9EP`Xoz|?I+4cdFDJdz#OA`?3{o!@s+gLDq3n0rIAZASSS*Zf6B}`5R40 z&k%45$}t4-wXkkZvUa8ZD$N?x7}-Ex&@pH}Y?ckm1zo@SSKF<=~(b?E0q;F4ivQa}W=NEuGNGcCo(7)fS!N>Bf|OpdWUmE z4sMR(PzIEJir=oX!Re}Db}R_**%yIP91s24X^Vc>sxWQ#Wd7{*o6TX7?fp_=FOlif zE{<)RiSwPjaL<S`?}^M`sN33t$p>5;JmN3V*TfL ze`X7i7hfQ!Kwr&7aOz4jL}}xZlEkC|cTi9di*SA;P)>*tphwR9G7$+|#uCr##JBsy z*pU@D+Um(ll_&8EQoMQJS@1(9&I$OrETpg8ZAYAqGn9)I3lQpgTF+KNr+qNu)x zmm~{^^^H4g(W(Uyq_qf0rwS6XMTfJ>Gzemn6M)o)A8vBV?5l_op0JkXQLDmPT}+k3 z1rBpJ4;IAzowy#Z#%V8_pBN*zD9i>O;8ugn9bukx5cBG#IaZW;!kP2(kBA2B43R>~ zCukARbtX~+cj?D_!Fl*o?fLQ9z=jKuf_?W<*;nV>{vKN!x zi!k*XaDHh~*#{*KreowiHdcTda1}itGw6ehHdNoaZ;%@2;`faP=zWz!06j!kh%i`m z5=}LA4f^d}i0o|1!VMZp=SDe5)NKVEuq-2lZCgNoUCm65n|dU8LpqQ6E&z4Y)NEKh z#5ea=`tB%pRetV_rj(6UGiB`ROZKVip^jvHN9WFQ-Cn!yDMpHP>#QP6?J*LMP4+v} zdA2D#KiPkV-3a&6x{RPfvc?k2*B*K_jUk2EK^wqu+cM%{7IoB|KrZIayC@fncTVN+lv@~&G;YpP)NML-b!nc=fjhEjMoI1YdDQWwEk%n;Je3_N=`I|)k|DQC#$=E5M z!<=5Ty}sSMPV>SQnz0uxztYhP4*1erj_D&Wv`f`Tl1?OxQ;C%LTmBcIv%t*EK8KVu z7Va_5kx?__=K5Kysjn`2Jk<@7c;O1$>K57RmGqv|{U=B7W?8n!8q zf8uj&U;j6gaQHu%gjef7CJ~7G>5W7xG*C$P|ijo)NLx^;&_y_t8X;L>xEW5 zas;+iR|v6U1oRwZQYS)=eoR^ZD0odt&^JrZ%PjF)(<#*=kwzW_u~U*PBX5AU?M)0j z0lp#|bw97-XsTF}vi^ngjY_`mWaLj(fB4@O`bX(YlXlBnN`eaT5V0 z4qxt<$1*l?{&zX2X}nIGrG+RsvnihQG9l{a6F!4UF;4z31~qB7pZO=@By*pVEdu!p zf_VY_EJgwRY52aGy&9-~!kXvNv%Y-_&fdW9lQQ&heyJ_vp!o`}(!FBEpH8rAhkt%c z9#$THr=8k{>y^(rQ6#S!Z)w+6ZJ&V zH4u}6sGE`~Z6RpsU$x4f?E0a%D)oHc=gmw#vX~i{r0fXiWt|5iy==ZaD|}@2to$Am zGcWqExpQ_# zK*K=mA?p$fCo4hTj{f3AWG_nf;SEQOFnniGhaVZ4H$<>c4*)wtUsrYd9x0+Q8_k?u z_ylo}rk95J+Hk#lZM$$FKiZ((1y|nQ#~{8r9c2u7>oB1^nuC7;`26K8g_JGRFzPIo9EZmpfiNIR z_HaY?vcQZING?!fGX6r?%K1RrUODr%y0v!Y)_CH7|JeHE?eIC`tLyM7K?Zi>Klo(t z`8i@V^E`!32v6cP*VduD7pT|$X?P-5;bpHA#pi$L<^5HzY9kSK)ys9>bCw!;B~JKp z6SkEzp!~aA(#HGA!o5>98cYc7pjjF7B`0jpxa<5RFRZr=ZCTHZ-tget>n%$jUOZ=+d ze52Oa*_;kkS|9dTrrh3~8!xx!IUFzI^uIb@+S}VrK9i}dmLOIZneNcP$-UJ-a=)t9 zU)yniEK*-y8q()_`>Fe5FF5^s^y0O|l==uGqrV3@@xeXjzxW0T+;tn~YU?|5VhzfD( zp)lV&Bwp8HZ)_>6jWznaDJ=iZC%qGqN;rv%BM<~!Ve3AKo>ZPbwpT5pUMd&95@tu>3yspnL6b3Cv%)*I*V z_qGS;D?C*wi2AW!ZsR`ysj%I}?e$g4_ZxF*q%`_V@7j|=;M4n!hw=0J!0XrH>kF#m zk5k#lPZlQGu*GI6Bz*1>BykMapCsOuSfQkmqWXs6aZG6f2`r+Tyc2V2OK_TKr ziJ`(tndirBd$ljN9PVhtiq6JESg9+68-#lS*#2OyIv)8P!p*V4BO^%aCITNFyoVhs z5gPjlA&~-*74Vp1?zsmLR=Ib#ED6o zjRIGEi?V#(7G{l~`q5B~1gZ=hNQj^t784P}D4{P7Okjjqm7aKmcUSV0kEY*MKh5w{ z-y^Y^62=hyZeKF^6Sabm7CNnUoqkoxM2x52jSKFQ*U5oxgBfg zoWXp5A)qK`_(|@!WIWBbjZdB16XT6FX97q0yGYl*2kDDS{k!rnN z5>M&{Qrr_d(UrlDC3{crX+g*QSr}%w*^Bw9iE#IH@cQ7C6cYi`ca%SN^8RlJYW$AK z52C4Ivmz!qJ{kI?FsEo6hkMzy8sCHJZC86<0#0qvAOtQTyOGA@ z)I|-O;aiDyAw$>%A|!^Ww<}{jMijpF*}|-tYggYUfmx2iW{x%=TUa*#!Pu&0X8o?n zdm3ApOO(mM{m}$s9+Zb7*h_BqNL}*vsVVUT?K8dk{^KiPI$*7hrFbyz_Pv;w3Oz>Be9y5uhK5@VR*lsGmL3DJNX%k*=cS z=?kq660TDgv+>VQD{RB;md!bo>80X7fHS@I_ln>hQPcnAC*p0Xp4YIKJIZ2{0!~5mC^YwSh-RWaNy@Ds@A1;09!As~wo2y`bRTAZ4ey8>HN3=PRqMWa`l6>$aw3|H*a-mAISF?J z{>$;uTWBziB(>3pQFu~_Ol93$bkBSLFp>2|P7sb0KF&~#TiEdsLwWN&akj4D+Ez~N z!uD0zLydmREuaI_F|kZ(8AZoxSk#8)=#EwrE}h>qzfAQ`66miC)35xh6D}on83Wa! zcr~$ceQMD*_YNz<@D=*$bhBoCzG+Kb>V*3+C=g*|C&~faod8k&5vXe?yK2F5p1~1Q z48b>IJBq6WF7Kg3FFg=rcA8Lr7IzRLP|4|cl>~a92D=!2-r(zYl@v2oyrfdZ$+FDg zb)_+4FQ)$67BZM=YuW!tb}`20K~mmMFW1}I)X2hTRZ&qAM_Jj_Bi(d3^E7(UO zPPW|YwroE&M7GTXL-Y#W%5q-x?_j~w_ z6s3*J(o4IM67mTJSr>=xV!&W4Ws1zS3mZ=kOIo9F5!ea^Z2})xTj=Xg>&6VUU#p%o16c4_Ivv<|KG>eYPA}pfQCL3Ge%1e<^wfZ_hpw* z-Wl@ef&AG*P>##+>AXy)JCc;^cx+-cLQS4z&=1z0~a2=coAhq zzpBnqKZujpo>?+R1kyfes<3VS=$&*rLw`m$H_hqAE$&sj&*JWN@2jCHS5epNk=h#uyUlj*!(5QhC{ul znPq_!sXi^mG{N-clQo`IZ(H_doMNLgEo7#R5}UJrmT}s>U6Gb|4!&i%mUTkDWwBOz zc8+%kR8Cg0RDbQK?7OOvrJKji1&y;a&(uCW-TCWp>E`)dIpgdsro2nK2;5EMF6YW{ zM`gW><>H}rIJdc^{!jYGvW`~Zw(y8}yrrUw(4ccDo=VN&oZpzb|D&7t|9<{_7xaJk za$(=mJM9n9#dZVxe*9;*y|dfyHEhDHz>!_-X@bm1wr0X9Q0g>VbLGG<254*WzkOybQBt;Q=9Wf&BAPCr(8u_Hz3F?e2DW7smg6Tu)G|h2l7b(zGah!&4^v zz3k)B;{()eHX0RgSajltoY-g>x~_aZ`e8`omFH0|`!v%VF$g*$zDuts#N!Q|JU&2Q zZ)&}Sj;YNWKDA%n=H+(^4J7X!QyVd2g@MDbBQI365rp0=_K}fb3lVO$H^r-`TIx^< zrP4U0dj2t_gk6_j9tX~aD|*+Md@#V4vi7!G=!}j=Y9pQY&pA|h z<%GIKZc|D9{o3(t#}l|-$;J_UWJ(|KgdjRZSQJ0{@G*5AOZv~rojYoKO)AMYdOJj+ z#|Ym-9yPq_F!83{ZXo36AAXS2>C>l;1|z|RW06;uLTjakJI%i~WFRrkZUL$!qo2hy8t zE4Of~U75OiMSqDFPK1$e6*+X66=2`-M(8{o5LbNg$+Xqp#nQVdURFkgbH^LC#Qe*N z-Fc$-O|{}A^$*>E2fEQD-am+3BpN8nm+C(H9bMXYj@mVfZQBte3cJX19fX5XD2BZB zE>)fhVL(u}!O#Y|l1`Hc=u4~R(ox$b7sUOt*&tUuz^{T)x?WF+-m>aC6NfWdcbbGS z{fS0EB2bXb3m&z}DRHH5WtKoB*nf^nsMR48!RRjM;+a+12KPIkE0yd+E$Ta-7zn$ZM+uJ+4dw~D$Dv_Z+U4)B%ye2VsV=%)HKRW#vZbIjLZ07}sYW zT1WL(`n*n^yDUyeMxaFpFY7mIlE=6vR3hyYRZZ&MD4&^|GiFZh6hY0uu-0E#Gnbb7 zX#Fl`{+a1`sTLI@C30KE3eF4Y27N>NKqA!qYZEp5 z&Dn>N-213?p4GWjGEp`YO=wz%X@v4l%eKXru(wSYD7Iooc`vx9!cc^t^OQQ!`SOavavPRszK) zzvuOTCj1xOYH2G-ASnNIz2Fr5x4pmD&Edbj9_at>=gJM9V1TV?$f&6!F%QURIOJm* zIDe>Z+UGy0DYMr&cRc$btqN*P2*)<&_#jrHm(4e+VJC(_Vvj3(!Hw6NZ^-d%;&HSq z-qc>oErfmN@5-qeeM-Wyv{RaiU!~F}R?;KXiu5+J_cM7%N0*7FJayBhqud>z+Ty1C z?RcDwGzD+S{VfBLi4oyj$aNUsLYJ6Z<6jq&$d>NTS&#nswksH143Cz5q)ZFQJt}X4s=cHsWkO?jtZ{(@j?a3eB|%uvUIyDYBmj7 zij`jKgC4cXEG0LlnWzcl5~x3vdT4@u^(XOcb>>lJVbp{X6>1Cl>b6Z>Vkjf_eP+xP z$D9m9mqi(~v8dMM&sbG|tK5hg@6zGp?3`!RMvFNmn|Y;@HTRoHu`gwubQ?w2r;w5K zyW$%nRSBig%?XH*O6km+SZbj}J2o=H0jCp0@;5TA%i9J+e1=CcVUL1XHD!I|skTQDYDVMyB za#O`jbH#k)KbeZ_xCKlZ|GT?8dHlcoya(g|Uas^27YlPTmQ%L)2N=T$71rsLM}U$@ zy9nqPA~g&8hP+~OnqxDcNQ-$uq+1O;kb9pa&53dYoCwhHZc65hy05qp`|7LF*wKlm z>M`wiI7j+FM58`aMX~on^wdxvKNyq``9xmjt6E%{>Q1S&44KkNi?^c67FY38fkUN3 zgNqr95SNlIJUWO_QYx|3cVatmY6_iizBT8x;M|e^aMmp&l}1_pmlPrP^oX5NTT=#{ zm(Mv;VK|lVYLt6(B&q=HMUxYx~pGE>WjT*PQTEtP@zxBs9sL^MDf*t6r$W<4C*@k(DNkaBg{L>rM% z?2oXS{v-}Ysf@>2SkULwUlP5v6}fG+w9v~`hba9{wjV@F?kI^=+DB!9--;bv zlJzR`ZJFmQ4~@(2>zWV$>!GQwH`6%4H2%L+!2f%DyMX`i<*H>o%WDE+j*w%yMxHP; z{+gFE%*}xx^EK1>m!aq7e_Lrg#4X4F?tVf4x3{|o@xSloN)KzZ5vp_1!>&KZ(U{j8 zVWi)%VZ`V6)X0!%=?!++_e-R5d>)}@VwHkBI<7N4Ls3t3x<<{0|LlLdu0gGrbW=l&`vuQ99 zYMY?`)$s#*b)EWKcc-GcFKypbMy%LsrOeHQE6cIN)DLN6D`v_gT%XdCXw`Fhbi7eO z7$bGR;88Dfs{eTMPD^J>68+J!uzle$xyK@x@@@M`xfR3MT?P-?UlwQeFb{>=Mvk({ zqK+Y>*kJWmrT3~Uywy{sp~3W-Ll3>!%=<{d7;S@mb6`W%GpdO)ulDA8@EZ&`FQ!J;-kJ-r+e{#2bIDYah+eR^uB4TWrp zeegr~hi<3U858XC@wI&O3HldyGFzL~2C2gn0T4Wg9~$$>TtfUBIh1lwx6sr5?o;gv zz;VDMCF%!qL4tebE;aCfTeP(^@@O#UfsoVq|K9d~e*bR|&i}ce>j^r>oRh$ld*M_o zqsuYzR4`@78zFu04{JA`AbB_(3w@skoFO(Qu8T%49Vjd+PaX(}i@9?_kdOH|bsu|n z;|cP}NCk1<@B=b*u5`F!^ry`>`q^`@5%uJoB1_080dgIWv>WZC)BY)^0ckuz+JXI- z!&78C0c*5J4)4hSs_Zw~gFk|f{BLw)JnD%5M1QgiuahJ+z}9)_qoLyx)_B%tmww|} zdw|ay&)R(AH=g~c@dW*X1BZqTy*+xv8f`zIVkK;}9h=~ek_+f>jrIkzs7*R6qq%MsipHNl{gMqe@#{vk!`GRN+H5ShObOsT!1joA zv~)`ew*=`8CRpvd{U64!UGj41V%}oEhd3ac=ue-{^1&^pd_l;!QsQ;K@v0EeWXea9 z7g|bw>Q@?#x=~RY|L_0xfB%2~&;NQnI_nxJa)tl`<$t069 zl*SZMNp7kGbDuqvr#zmjEy{<~CXL23^xyLbCiz>V@qx;H9rDCZC))F7Q^MrgI(|Se z9JymgZFNc5Aab2^g1)5D)fZ`%{F1})U(klAcoMkR=u2{jOZ<2f(Lf&G^X1ul#Sy+F zUuicQXB4>v2Ob*JOHrfBX-U)~BZ=S^@}uL)NYrjmnXcz#U%cL}j`VJIGzuM?q?%#A zJ@1?9^v%PQBi3k~jfwoF_U2!EmOS}5>I$lUC)krmn#ig_9=+U5jy}zHx=(+1_)p$YtfqZmM>8FVP9}vd-et<3RScG*{N9so{*SC{oI;m=qnGva|y8S!h=!Ezbz4eB) z(fIP^i$y&q*seSlR#cBX)mc|ZA&P;nF^8(N2-H%UdRKWFi*y@}{;4Q#{H&yK^5h74 z+=LJkS(jV?B}Wh$m!Sz1SMSG%vtrkk6NBg@A`7y6JL*Ad@1ev};?K;+)Gw3kmcH@j zpjZb{GKo|i!=hwykfnX{1by_Jvb=@SqmE*S@^R`5i3_12NBWgSTzyfDkf@>pGNBiQ zp{V?+9li0kL5476)N%ybOR7fIl&oy|8J4av)oDKZqX0|ri#rZKIDKcihyJ@s!0m2?(~vgrP4Ny>|D@ zmj0HQZ9FQ^l5!Yfhz=zpzQlp8s#0XYaUFiG>PD%%8;x)1ALJT+Lt<4%-^8Gn``^T` zIA9q1*7(*Em-=t?Pv)2WtMM&~Q1J~>sc1sq(3h__U(mOT02WBTnetXEzC0!Bk4T`@ z2dS9aE#mmdPbJ;%`x)_p@?noaTXn(>@uas~IUOmgY? zG&1QzK$~15k^f7+K8itYQZL39nasE27(J;ECz7d`V^_D#>*l~cnY4VGN{8c7HUW*4 zQg29+#y6C8ZOZnmW~>)&9>lvxx0)&Ur{+OZC(x9g@UDXREIfpdEc;wtFYav_kEP;x zERUZf-_H56&OAFDQ=3HzY0uhQr7?YisTXWarkCEV@l73V_YHmFK}bYGbA$p*QZ;Xf zDmLETMhR2-iHSq8vT8(Kl4+RTT58l;*1};*%ga}c=eQy%Q`j;2e%dK6Kj&4%c0%9o zM5{E5{--^nd@{$`P*e0jJNvuO^ZMWY9>o8-mn-9-r2_tp3ARa~-LV=50gWeLV|%2% zwhTi2+9%Z#NvzqFobiLH5VVohLuyhCIn-0sDY4R-GzjX`NhPAoNrOTK6e^%l0fh=E zR34E+#k9Oi1xt2uxU==Y2L2OsRmT`Vqo0bFQQ-t!iT`@t-QG?f|LyPXg8lzqu2!qn z$nF7l$Q36Zlbyfd87EhaXx&Mcos1V!!O2Ll*nqk&30foK$!%WJxj?FFl6Y$u-S&(2 zb`-_gq28G@A&ldRe;^NePCU6%z0TM#V~3)V@`5u9X@@kON6g8H22OIyX>z!JI)-!P zHg`;IjgjO{LF4|Bd+^TDzEr*&2G~8w1-3zmjcLF?Bzb5de?mR=$ni#@i-Yt_4TP9Q zeR6T@^BI`QQ4tui@tb40xnOG8F|hhaoS8#1%edB%%$mgoCF?rk(`{jv{vmJ z1w<+&LNAq`J}&{i7nsiRhV&qP0z@>X#IhTD4!`cm;o%HI89vA+7sTx_XVk)hHFh|$ zco>il_MMi@x~Ep9iTxx7+)p!kRVPL$bwuic?6nmwAUdyHB_mCSEJae$5gs{tb9xqI zec2fK+E*%?_(hg9fv7OY83%Nn{~8ldPIqE* z2*k)wWnvOCj>hT4_TqrK_8y)Ci$N`-c^H|cWQ547GoilOxlsp`hpe3yiT4Rf- zhrDnyAc5$^$}w8O_9$pFwk+avxzOb=y%tHkmv$b!z^)@Wex|GF^$~5ix1Z}S}e z6pN_*d=A8HzrR~j0WuyQ2~0I0mgRTE8}aeHb>YglQ*|NRBV8eedWfoM&I&Q5cxP24 zjmHbFBehb+m1HPSsvj+RCB>$|{9dn;3W`l%oa$*GU5*`VoC+E!$M&0OpO2nGOw*L$ zt*IH=JPTCMs@S`(F{!>*bDX|85YXasEb2Xw%7;|em4QF(Bg0!dv zL`+mx1X~ORK~tw(y}>R8&?`$7+3%C+7V=lBWz01y3&F{x;fC z4^FGH6g~vQ)89FD|YDSGC`Ack`BwLXxih_J=v|b@vr!3++CnfVrMOj1<;%MWdSox8N z(EG45cj^+0hh8FdI_KotJdBfT>X?l#bqv)EMSJ=o6-GTELlO{=8@Wz4Zo4uAP){s_ ztRv?53x`~G^sdd;rNhT9y*9}3@T~Jh{xh|H&VD}n`2fALZA9f19x^fvU45cPJEZ{F zk|cBsg^v9{PZw2x)K|VzGu5GDPuKCPBdSulAt4$dK{Q3@nbhnZYEmj&pRy$?Uu&XE z8wl;H0<}c`DxOzN`swScm2{ET1p6($kZ?M2tkUMeXh$xC za*2FRa6p2s%1V5VnPX|^CV7+#_7DjIng>zEs;KNpQ)_D=zAGl}TC@Ap*O9nd8)YtI z4F^qE1wmpQmgZi5H&=DWk)Eh3P4 z>wDyfrge)6YO3r3#U&}qio*5Ts)B{5;(~?ecJ&7fPwBVrXyFMN38wYhdzy8ktiY_J zE|_&-)-jc6_%*b3~mK+}ODUUqwHiogHtNjfClSGIU%{f|Jnm9B)*QG9Lxhm+mAAYbg&pjslfN8*Th30|jlN z(X8A~cBTFF<<h~n|o2Bow&Hvf_^St&rRY502c0|V;)$2m<({CNhu`NLy_I+1wrc*X+rlYHs z*0ke|2&-bEmBz;W%$&)OQfM+{Uwe4sSSTQufg^3K3d2+^rONX}ZuuJ!=?9){Z_5@e zRRHw1Y7vi&fnWYxRd_#8dF`u-k^SvUE3&Q8VXDhjB$?5b)>XA8x1#MSS(R%}0c*}| zIgqNSG*Is9)wEErh6m9R(o=-f7E4u)oFbnDLmEub05f7E=>tC^S8PIyb}%C)E|!s#Q}hbW%&| zJPHw5F7l7*B6dKhi5yKA8Dln{&}tIjLO)?Pem_-8_Tvej_DQ+czn>~5Q>#%(nOu#M z%Jgbfy-cr0{l~p?T6#6*;=Vsc?qe;vnu??ewL>?YqJ_+^PU;Qm2Z_&P(kl8<%*3Tn z@RY%-)&cDzT`61bL{%)b*`X?Z$*F3FGM^CXgOA9SfMDvt{}FaP79k<&ynMw2T;ZP- zUCK+%VQQ)*b~`f{ZnU3mRx1f>wyd78)HbjgN*ceVGny?Z*3*2Q=@5wm>1Bf0$ceNY zg}PJ~Azgk)uL44sarmoi)zAVDB@W>&?ADT)ov=Q%Berv;8&-N zN_FW3D%OQ3%b2OIkVShBV!}IZti^4o_i4xh7kBAwQc=&Gl4PpiMj<}*VAzriyV?>W z)~$HwU}cI^K)AZH{)Lf?vD&7gG?yvNqk4>oC)2tXig+BpM$~h!Bhwaob$a;rEpmxq zR%}Z-0 zz#`H4vNBUET28Ok6X@+Hlp3ncY3)F9$j9|`mOsA|qVY)lc=E1>D|cz$r*8~US1r#= zxqa#FT&^*31fvP|qd^(XeX7Kj?W8`9oT}Net_51<<309gnxYP)x6-+fHv5E(L=84| zI*F`W)25J`r9t&<>V@(|rl#1qG%Z7DmU#))w1*e6wNz%K=$h4hR4dUswsSOVo!7OY zqdKOiq{}PoQ>|h}7OMK@bTorW9o00eqZ+1lRNuUg=638s>WJ40<9T$MjOf)<&9Y4; zi*V6F^SbFEn4TWl^i;5n*D4pxO|yPNVXtZrAXEYUjA$=HbV_Ccs1E!Jmv@o^zrx4l zSBT;*A;&nt)taCrAoOv~abC08v%4)tJ_sj4WOOE;9fd?u{)it>-rcscWc-@?TN3p& zsNRz9012#9)nb%|_u)|0IAyAsXr z$AfPEN~yUmO@31z$tBir)LE57>`Vt+? z`K8oLZ))}WG^poFonFU%YReb1`>56Ou3`qg54jBYSw1aaS#HRj3zBvzJElRY>SuaM z0(x~_F(+haVJUp_4v~=rAj;$9>YB+AN(bA$7klVP`~Rs%tZ*Zv>f4Bq0~(IT*;<*q zJi&~9O;2mPG{xTT<*?A;caCt#=>&7fl5t$IZL@S9b-<4yxh!9mIQDED*y%U{5$+Nb zG!2}Q<0U?7ztO={9iqN`f?KIe zl=VR>1C!t{R(gTISV|cD#lT+-{KX3Cfxj5|i-Esb)dl`y;4cRLV&E@U{R;St<*vu> zFDB!5ms)*_$}c6K_79k*Z(9!WVUM+KOqyctuNO_``wbQ1~wY$^}rlY*_q$5jM) z2k;K&Pk?s_;9cg8&Ky(5Q7*Eb!L zFAe(S_@0XC>ec-@xt?*;O>urrPMwr<+0b$8LAn}qFl@|$jXAfgKiHU4`mHS$<1(1TxCs_ms2?o*Ex9dG?9$`hW*OJOLYuXLuMm)W#hS~i#$A% z&3$I%F}0(EA!j}EQF_a~iM?IwoRpY+bpsMcy{Kd$EFbaK@YPt}$Y z=^it7e8g#G-s2PSBY(SJBz%gJSN-p;w}n1XF8+CQTS*`XvNh&A1s-cKe-@Oxt7kdxpQib71tsSWaNB4!M^DEU&Zt+JJAjDu@kU1dWD$d zjoh;SpMFE1x{^*@r0XywTHyk_M4tudA>zu#q$E)~EUcDW=yFUM(Oi|dwn(;lPOh6< znGsTwq7Z)THPzBn7?klkB^^DR@>#Q8SYS)ip0e!LK&K@SXF>AXF|!89J4+-}Ckw~e zPE<}Ee5aB9UE@3R0B)I%an4CZ>vCgieHXvclCFl0(zhgAvQvI*jMD&*h`#e3T+4mA z^xWxKV;peS83lNVJ>0@0;_+5Ak94fSW*vPTl;e%s77YkxZHrF&en1C=wXgBSt@uW_ z+wJb}?#h3=-EQ{ZUU&cb{`Pmh-MyXX-RIrCoxSh6+k3nFJKv%1eN;fmI1aw+-uhel z!mY^FYPA}P3hMx2-yv6=cyg&}pZ_3rYZtx7x#QUf=rClQPELs0(Qzc^Ot~&MCWK=f zbLj?(J&$sQrPVy54?}TD&>9i1EvE88=(sisWa81MFS_j)?d^s7d42H1@MLZYQ^S8Ef25QTmXibvI zmN)8+P|KLq7o3v1*+R)7ZH=>E$87DX&b8CE^{S5Qr|Q0dNTPp7QZqJf4%YIx*4toz zY+Rmv4yrWVO;dCYX1uyeO~ToVluEZw_=3^jYTCkr+~{2F4Vn-?&#=j?IyU)1Yg7{T z6;!bnJ*y@4n5q|4+Ow4KJg*p)>k~K@bXF;i@gyZHlAQ>;nBX#5Qb`h7mE;5$bOK#x zI&~vYKQ7Jb+eiT)5PM3YpXTL;Oji|ZR?KMjkqIr}j0EzP=F0ySZj z&A|{&P0Ck0^1S7N*|ca?vz$s%7qkKlk8@x!O312;6e}4^X`1IaC)qg`}z1Xv}NCxQRj2@S0%8aOMNhntXHRsIDS zS5h6e9GRf~-*7*GK-YZMein&#&)QfdBTbG;Nm8{Qct~9wK-oJGjlHT<&bLJn@u3=4 za!H(cQ+`{U)6%x9e-F)E2AMIQy#P~#+ne{k`tkPc!3&rtzt?U@f1P`^eV#hMkIro+ z8nzL64M1akaqwsV;l3vN_UOin!rOJ0H{9^wKxN&b0GMyEpo!Mq6I=nV2qI-UU_=S< zKK0JTOnpVML=V?I_{Kh{0d$}@^fdDmH=A~@02}NT&v3)k& z%&)*g-0^Z?SM6G}-Q{uVuy_0U;A-uWl}A=?TV&;%Z7eIVgi@h~cH4uWe*5`&`dclY z{&@P|#p!oRLm-je@LR%$LOA;ZHzh|oue2ak5}X=37Bsho>fj2wYB3r{ND3n2jMPj= z?f8b%U?WC=%?u(ol7t9BXT83_*?JR_!6H?T4XqhuGxuOI2oG$kF!tg(GJ$OVJz*(( zWG9|^akG)#S%{NSxS7N-gXm`td-Kl@S0DG(9k*s^T$-~OjP(Rhms10(vZp%ae+XoX zW1szt&-#N_x46Z>_zX@e!a~pmGoY%bhP$oqK3iK9vF)KddI4c!AKqa_HB7icASttiVrqLKjEy} z_Z(^GMLkJeF5+hU`2eu(1~z$%?qFQnJ+Pkj67^nG<58YRU^(}G zv4ijYo}oDV)K52Zb0B8(j%dx+q&ldWjKXQ|L1G>x>_KRDVKe(snSFT7P9$b02D1}= z*@?UCLtOS@EqhUxz4*$0WMx05vL8*^kE85IQ1)Xd`%#nqc*$O4fd?abH*{nN*F<%Y z_t2pTK}7ar<2g}~9|He)cjV)M+7({3;|IYx-U;E@LU0aJy$`mrAKKD~XS_3#aoC;? z#4z@cBLKH}cf?|gNjx~3@mR&p%nuR`M=9RGAs#j;?v779raVYECb2$X4io7JG~#h+ z!@J`Q2j_1N=V=6>3h$05jOcJ;?ruoJ9w_!*FoZpmxMPMZT+Tk*$bmZg`pGulN>TC| z;E`&}ociaI-nwMSG@G1lf;hLhVFf2+axUE38Wi^{7 zJa20tmc(mI;$)Wb*-4It`Zv*Cn6C3|`#NyU_fSFp2PwtxH_3Q{!kmTocQ3i$ADC#{ zK(T$l3o!tSY$p&hf1Y_5CWRM~;y#dKfk|zC?1=Z#t6WI zJ7Itkez*uXSVRz1#4bP)K4`Cp0QGvvqkDfSnH_*+A|v<^7{N}^F@FXw;34ZT3cv4i z@G*4hePskObg9$x4k2?j-oD`p70E=J{u#WufTSi$s0}b%}&vC z?n>ePrQ~hF@SQ&Bs-QKhepYu5)TO%H3{*jbe_q=p)i$%W-}T1Z?|H4p9k_pYd^vZu zv)(#qoq*i-x!DYowU*1i72J&5yLZvE_bqns-xaf_{!q}UL~BG{1Oi^#xFV?WZ}XYm zLo@dIvG<07I0)cF=E)9pfz87<>OtKLgxQcxtd$cR=Cv7(bU8_8~swN>?>g2}6-PZ_)*%%|( zH(DJrzXUnQ>OnZmS{?QnJ#cAJ5DB`K$9ED8s!JP;cI=0-3$o=jr^&=YhT01p(UT44rDjczaWuBvI(Shc z;7NudYd|$#(gjsAoZ%y`-JGUyIMmU%4pg+F7(Vsp^=p|IuN!yZYeVq0dF_pZG7Jfv zy$NQ3sbR2;6M*`mzmZ0|O0{>d-FF9Na~c1O&khbu^m1VL|HWso)$;-UevnwrZjk1% z5yrdscMO+xACdMx91}L{q&h03wi`a{r2Bq_*4h>d%kK`gwJk8@wt0M~&|42x)QS5F z4#D+O-44<9h2q-g3dyz27n<8V=MK?ro^!;SHV-+%O`C@tk+01|j^NkkA$KTm^PD@R zw|UMJ+S@$n3Gv;a7&KsC$nOTtpylv|0Jms*0)1OFgP-(;1~*UoLWFG#7I!=$!?x`f zI_y2#`WA7A5Ze|KN^F}yq}VoRXt8Y%5n^oH?xDur!3L~#KJ3r0ZRb$sac3wnWO@8J145X4hdL^hIc%(Ax9rj|;I1ps?U++jbA4_704) z9fPU&!D{!za)QET{HrotkH$7%U78+?>U|A^VfAWyJ2<9pjb|Y`Rc%JsudywNAMIGr z#(#i2Fq#2%Z30A;PTiY;`Zl&bbe+?%x;Ft-k*$s6sefbJ$QqmedN{qq3$2S2G(a$Q zkE)LoMAj608B-^x?$MY@^w-U?ZFK#dpmFxpxpCIf2_m{(YsXnnCy3@y&7I!5IzbeJ zYwzg#IzcqOHF#_bl~?6-!jw`NbbdjZ8}!^YvQI-bd5XW6(W*SCE+J|B{MTMYWQqhB`qWuspfTJ+0CzwE|?(JveQvcoOqhEIOvHSaF!74KX2EbadmRvX!Yi#qL$Dr|gvC?)dCoVHFWZdhH z{MmI^PqwL#cT2)rRVG@MEJuS@;Q#~Uxq5%Dt9_Z`i*l{w^r0s!QW{t?D8@a+fRl#I zz@Cn%RRJdJ2v+lcgVjXTs)$+@QL7>&h^SS&qE?YI1g+{^gHg4l3N08y_>dM%QVz~b zcyO|x%6m}&iG1fh=Q~jVi2_IzKq4cE0?3X62xWpKRY|UIY4H=-HUh+vr&Iu{fWcj8 zizRTj98!mGE^o;?7{ha$V}X%e%St+WFAL;c5zF&^dZ{XE%Qo(>2bszihSB z)6>(hzW4(EdwP1>{qN(`|NQc+FaGlQi!VR_&(r@r{qplK|8n}-e}3`Vmw!R0+obm2 zDj{+Cm(zQHYjbcPNSjP1A2kf?iDF*ltt1S>&ZoD31s=M3{E=d0PtZAl0=**IdHqze z2Z+4=C?^st*<9^X!1dHQLweH!3Kq{YQcf0xP1V7jRWwUU3B>KESEtjzO+Whxp`;}6 z`VGyAkT@@%AXY&&Pq;78=C14An;)q|qP`FQxF@LpA%kaT`iUi3@Up&fC#Z>doOH4& z^lV(~emvKd;SU)V@`s*3KBGqRE3&G@+142JIbHHnwm`flNaUt~rJxL)>zglrWZ-Gg3h$J;5(xQRR8`4H7*h3rKm(L+B5t!u^#zjh@Cces2 zwNF>1l&B;LUoiSxO^#pk`W3)Wn7@L7RVWKD;FX}eSuEF`N#Vzzo%R_zgdArcmXVcGZIVn8jtI;T+sxJYO;y)l1Kgxw(kR^ zLM)O=Nm9j8usgVLpoGYQXmZr+Bz@qa4Oyp!sn)cIA^yY1g2M9dAIQSGlZ|ymB%I&Q z`u^H7bbA0>>xlUav$sB!#WmRG8ji*rlE>}K;SNm8-o*!Fh=($jYoevEt_K%rbbC93OuB+;pt(p3+V%kC6{oot>xV{0Qm$f9y+fzXh zAC9>*-`$+Luyi}EaZtgFl zvR%!W}T*r1Lavj|w*NI%GZ$uwF*YV4S zAh+J|Tf)AjeO2Oh>y|U2x=4IUaB2wHvU2T;N1oUu@1ALO%@u>tAB7+}%6X+FR(C&# z6$NW{Yw=-5NDA`)jMO|?9q^`%2o_EhluZr}M!F;+LeN=8&}_X~-sz?By$nrb2v7F# z;|Jlzqxog6cqf-%@tj9GN+j&<*SM@$_6mHHqXJ+k{77psi}E}O9D0JN=;*m4!^}T zBk3`^u9Ae1l%z+)C-nkGkt$YYa?3{_DRq)Ej#F`>I;H+^lF>Oy)=5TAK2|>#ODxFH z?hlwFo@M;*6-jBSPHEV!)_teR?iTf7FlBwLw{38W^yhWu$j{%tMWrRW`>HQFu*vtFL1jyLCB?!qq7e z*_w@m(d8j^`$LLEwkOzeujvVTRRh6=ZK3DwxB5`vx*%nPGwlU0^CW=ph`;86*4qhv z4PCk}@dvUuoi%? z7KW}ChOHKetQLr?7K*ACim4Wgs1}N+7K)}8ilr8cq!x;!7HH=R!ccn<1hsJdv~cva zQ0%k-%eg(mti)`muKqH`XoZ->z8UP>r+8lC*0WNTMYL7v*~9x9GQ_j zaxYJYj)K43DRPIGhA-t^NDMVZJ!0oNnM79V;*OyX@&G80p0R`idU)B17d2qqXwZ(8 zrA9)4n7)rGKojDw-|E(T_6I%9iM*$&+ z=+`UlS&2?Rvj?dzhCRN&Oe%&w{+Yrao5as6Tw=E+XnZZkz-8|&CiH_yQd*}oVX4im z*D;AdzIt}w$pScp@Qg{r2K8GSRJ#`IpiBM)$N)RrD13Lf83}szV;LUJcB8EqYNF>v z8ViyM0WARnW2ohnJtJ%(mru}RZ?Bz$^GC2*qlV{!Yu7zBsVB#29&&7;!g*50osxG0 zg1@<)4U^SzS8Cad`q1q@DS=sY@J;nJ+}}FbMja-bn2j#yx*YIvNT@_*!?mnuLfbkI z$#gMAZ%z!+H-k}n-svs)5YX+$xZ3x)ja^nRcT>If74I(W{k)PD&ayQkZ zWmmi$H2?TVQ}VZK2VdxNVtDw~Gej03gy^V~w`+}(_2je1fBOvO6m~kEa|N+vF;4xe@(iquz(tsVDXv;Y#uxOPas};xrZQJ{km^Ik_9YnIvW@ zE>rYFeV5m=mtxYqqzlTLIJJNA*};KuyIZJeOzdbyG3?PduV2f&c-;gIz5W-Uy;gMI zXcvwaLWha|A$2=r%-CRyF=MTVW6W5L8H+JvTl8YgSd1BqF=MV4W5#04Sd1BqF=Osm zV$9e^`?JT40R(IqC;%A1^z-c)0iV6WI}&XWxS=`WRhY@jJd5yLlCrf6YziD0Xds;q zG43#?+jP3)&6NCZN}V*HGZ}TLVi^&`$CJX7>JcQDn4vc|1MrR7ZB1`8IEx>0o?c_v z8#F4j^~?FNT|7jHoP?Z(9*bsac&U|?O>!%kHqEcDxRMr@lsSq3fJ+XnqI#FW`_{kT zBxHi2)9`ds!c#uu!g@MrF)bXn(vG)`GCASpM9)nN6n|?N!&PB>CUA$^utU2yB;s?R zOl(ib_Eap^eAintHhc^yhJ415&lpn$Erxu?kk73aLq45DjqGW&#gNZ-`@ncpSjZ=K z;nAV(_$nQG0XN7QNN*OPt);ouBDh<-9?kE9w&QSd;hyn4M2TVDqQL5DQDBWXLaRlA zH43a-Eefp8p+@$!*`mPOZXXzL3KLjKDR&lGlSbUrWsFHsN&mh?Z-ccm=G5e;_f$pF(Gd7TMv{F=PLrP z+kQ)dw}*9~5?0PX563Y4i_ZpvM>@lN^#7UX44VuW}|4^meKKyUuVXKyTdULu@jO_tCveS7R%ULx`qBJ!W0%elIx-fR@16a`79 z^?1>o^EVLv)OvJktBX!ekk_2^zO69BWi?8{O{* zDTboO5PD;aXab*sSd6@oYdF92*1V^j2zDTK*qWNA0ip}KBCP9zZ_lscw-!=4Z53?m zzXn;0OSe*0>=Zs4*nPd58@lj}=CtP)?@A=3oS0yeOg)Y`YegrRm_9&mxBCev>OSaY zUZxr~?3Mu3rT}y%chs*dNF%fHaXOW9*SW~x?bcktDzzu1XCCYn?KmNQ6d!Dgeo{wc z&vT@YTRlmA2c3Ke4}MBM5BH(J~|2Mo({#AI#1nZ{q(`^=x*14QHwlmM6*>2b?(A3!b)e-k^Gn;ZWEGWr<4Se zJK?0bG%NM8JH2V&MYNP2O51uwU5{MbHOy4#gj=_+?n{XjI^ApGvmb~77m+Tn5;8OkPLP`7X=}>F=loRoSOL+R; zlcT#9JT)e#Mr~=PIVi9;_(na(|@7Draz=8tzaUsnh`9?!sGJGNx2}XP<+lcs&|Pp zbDx4^lX}{Bvvc_R2N!}XKWq$2i+9Gh?;<1F06))hp?3cNQS2Eu(f>sSrlpvoGbAWm zWJCME{f4o(w}~sdR3NnqE1VHibDSZP!DTW)l+U+r%VTu60 zBsL55=#o9sSI^d=tnVqv)_MegdouH680*T5ggm$fYA$f@x#Qf$j?@`P6Ej>w=xRl~#QQ|pfc!C#%$%*9+CrO!#6H}{|vc=Rq zHcfc`+WZ^d5Ysizvz~99o}Qk5^~D$P-_z66&VL_&_4!wiAAj|i$6tK;`G21N=joT9 zfBBcw&;IlA>Epkwonw?G&$8&-wr$(C&1u^*{IZZbdAVr$~&&5nMqHf|c2p>EC@z z+x|kwbF3I4+)_yq5I#wc^p!tRT5DmyaqHkB*tk4j1eH&PwS zu|S_vQ2}v!KU+F`bXb@X5XqoanLalTRYa=j{RPP&xwz%}$@@U+CbD!wT=xi#0~lXj zk`qjB9G=kDPs_cQ_A@vSe^prE(pkOGv?>&|{XD~F_h}`mkjb)h)oXwlCz|VcZ;6GA z%Trham5tx8T7E8l<795A^NeaR4rdo3B>TO*X8fPF^X|hHLJL;cANKklMwV`MOwWP( zclP0B`}5o7YFbS(qj>(lmQGBt#TDW2WnHd0l+q!soAPLlz1-`mhe;+Zn^W|b zo7Sm~e{SF9MvW|^B+ObrNIO13P?G0a=~e%&7dbCzFx$fQyBNOlyXRIjPx84(opxqR z=xbEbw+e(>B@06dTFB@Xcct*QSVi0=Wx+pNS6C=EiTXnU>If7&Sk01=_n^^eUj2^u zTME#T-c=GFsWoA$FAxnFuxfn0?^4K;ary0VLBtbDiZB|VLfLRiz5>hqu2{6nw3z4H zdlO(#pOj`$t*}ipFR#;COADUe2@AByk&}C|UFFS#ovHIi(+C@jF>&x@dH^T*d^ zx6Tum1#ts@v~_u%nqqUn242_!srC;nx3UA zY$az=u|ruj$#!=gtYe&sPT1j_!W-~aHziJWEp`}I1N2D}&*FJ1}#MTi7+%uIr>C)-r#w`{d2Ewu#7vuIDQREt3hL@?+SZE!B^19eHgMZ zfZfU6)D9zyj(@b`ucFnex?b1|@*llVRzp1`bB9`YnQjq7t{%7BHABF-b$!G=MiAkZ zZ7U$UV>Bxxi7&b}df-|6lV#ht0KI6pM6W@&gPU)hHj`s<@j3R1#Xl{1d^l}!(42|3 zAQ|Yj*yTgC5=)`m%R8t~)QyLk>5dEQ}!e4;1{4jp?4%}+?Sb~3t z9HAQw;dJQL;WDR1d`0K<4keX>@WAf!bI7zCI9WYd|4?WrKbm^3ED?QUmF)!Sn>lJf zw~@f&+YWmLN0m`VDFgKl)S_oOP6em#QdmYNv-xRTI^k9zxu=m~&x_Zr^rN>p*gXCW zBSF^uH=Y}a1X*rgd;ys7sgBebfY6jE5f8;ImkLxIn`&IBi+)^486tuODS{>L?_Hd7 zH|im>T_28_+kXkpt+N4 z>t_=gwjk4H4i4!P6VGD3<*T?SgxLMfiqx^&?e(@ z-edEO6+nNPdCB_AMXptst`(|5iTfg3?Fz!UIbZP#bRS6VsCnTv_l%k%bz%yAg*_^7 z2f}=NuAVkB>S3AQhNS1EF+JHBJB1JQU*$xiiKdNEN%bJD%|mo~Q~uap{}PpVflx)2 zG|O%e?GZFvfEmY$tCD15ME*@JZ5shWQ1;_}@`P-ld%1(5E=ebwLgtkMgoh2%q((MZ zLv~2McYp^igF62~k;edR&fQRZ7eX&?FTSa&Zd|FkLgh@?{rTfHhMT8k zB#-yv8Rr|m+gs^^WOjGSXUE0kTn(~WZo4@4=v+P=8rSz3PsjN8c^`c`d?yC&LhX@| z`uTy%zsBM2N%xRr6ipZxYTLJvonQ!)!{cn&=)|*^7)7axa0ky6O)E02L+V&fe=~B* zIX%jJh(W~xtnM7$q<#CSga+{C5T5Al)P-TA9M4wSxpGh9FbH*K)IslUNCR+90 zG0np&iUr4SopYXsX&5Wmokc3IPJ$XO>Wedi?0g6Y&#XYC!`h}}wR{!|tuG$6&Yzph zuoFjmF;Ild+2H%5h-~YYyKZ3}2OS&4l;h!U$7*PxdKG?5ulFociw>8-es}x2h#a2u z{q}3bh8-m;^7@cmko(Tc!IZdV;}}#l3vS+BF)7>juON4( zY@d#w$dvr634NygSV>)$iG70uO4M^u=FB)HENSUY&VY_-qNcF&T+ z2EB6pMl;gJcFY9dR-xv_c1)CDXV;3_rF1bt-^DA(x!tHL_mYdZLi3E_*0PN!^lR{m zGeJ1U=yl#S^b4u@w=I}jS}sH2cM>akJk1CMJ1KwK#XPIdpur;qV2ZQC;(0I}G(PwZ zQ~WG{g;yc`Q1wuC_72jhyKQ>uHEc%oW=)QMkYx10xBIO0(F&pbh{P6aARTYSVFVe2 z2K_x%!*rnB-ApjEX*odbo4lUPwiao_29ZeEcWQ=wST3_Fgz}zp_93m|n|`d1uxtXF zEP07Zty{A?P>kWN?K&ooeP014s;FJAim=c;oxuU7s>o2_8Y_k~h+#)1TOw8wf96@i zl))uQ=#@b2%Fb~z`g)2q)t`(D7y)hJUba%7b8F2!UsX;0q%|g2*4ypw?nfMo*EwY( zaY}owr_bQBdI_f=@!tHrN9V<9SkRzM<1 zNOJDtI(Wi6#JiSyVjZ+XqieRqkGZN!%RQ@$l;|%?%Mi;{vON*{ciHsrV+RQ{esO6%61da<24K$8hO%4ivGJm_ML+U;tIsP&DV4po*d7yNUbD zP?T;B99)oZx&c!!9k33h?oYhZO9c3luS`y11;$9xG3Vqs zIKHc|G9Yi*7+$NcH^C>Xc>q^6) zzKeGM_4|CcGCrui|5UceSEG}&TXj)Lqa>_kbf%XwJCC$=5v%gBdals#4r6Cr+ag&9 zsVPK`qEBLu4KwsgJ8g=Ow=oiMSiE>~7q7CyY)G{P3~FyoUH%3$Xk0HY;NBet6Dzk9 z&+{1D=9Hm#gbW224i+!IB5$V#!i$q`=M^&a8bQnj`_6b$!zHh*EDh?*0YdHQxBnYU zu(koNizomgA0PGr3rQGK-t9&VNfQLdUz&*oT7s-E7vrBv`ty5| z7W^|`mA|K@vc|Uc|C*(IB+$=|-$#fCbBmM;X#PI(zY6c#Vknp5D3@Ze2>sCQ5NqvJ zlD*J8RMxq|f?2zUEBK8qOem6uFg&B~vk;O-Fg#%#mVpIK;MuG;a}XoO-(`2uONsCL z(q+p)WK||(7zsIH#mKY(8m|6ham6?E;|MO#P8dzBm#z!KkgL`z-d!xZida5n{7nMm#UYM%2%}ozGf^U0p>M& zI1=b@P2khu#0r8hYoQgs^cO@r{eUBv`N&jtyWRE2?jr|+-hNJ@$X_sk=qN#f=Tc8xaembfov7$ zdggeJ{h_D(_D%JZ4KW~l;T9pAp9rz3Cuu7My4i*C#H6o*L4Y{k_A$}Mudp<-Lo7XI zbd>D@jg#GShw*2UJa*ng)XqTlN0==vscUFIjxvXX+vs6D5N zmF%W}=N2Ue1DxyYid1vL5`!9o&{VQY2U_wEuMcBVTaxH0&aKleCCC+apLzdaeI2By zW~M3{2nbtPSp!MZ3LmSP*M1aRZ+wZ#6glA)=#0;s{Fr;Z2=t!BWo$&?GQO^cpXk&< zIjzj-x9Q4|fr|6|eIne^w|NUpn(mZ?9D26oTh!+EmTO~+8C~es*;ABmlITvM#a|wy zDeVY@A(m#~HXoy`zlJp1a;}g|n+gOrKkcyq=?ayXzXDL;!E@0}fLoQmm0Ins4p$FB z^U~0hVe3&xD?X)8KcGpJ;T75G#Xp=6>FEogc%zZ4ZC^;L*H1w>8stTrVMr@uEVqJf zjqR?Rv0j7c8BxJksWc5U{{ZXs2F(spw4Yp#Y0>ZxZapYE2!qqrYcj22bWL)RNbcP+ zb@RCY;(f`!3hB<|1;y=#+3&(&D+JV}W85@NYY^<}SAbD(bv$G{qHaWw%9C)0Zytgk zQ3%w*iuK|8$cn%L?=(hXioW^&wYt9yQRfUH|6@e$^Q`o#?aUnaymmJEqWjL65u3+4 zVeZ#xTp0i(^r0~WPgnbf=cucnKS9fOvd$K6ZUY}!6bnNGphm8^U-PrPz)qSBYF+}5 zx;$QEX9?jS>uYDX_5{5Y;JQuWb-$6>Jw zq06F$W_`~bp6IN{YpyjNp(9Xvlz-FK&Kn&PZbW}MZ-p7AJ3-meF+z}ve6_8+!bWeR zz+{k!{=QXf^nHNrnz6HB3k-m^Hr>O&v4-*&boimx2$+n@IKKFJA@YQ4+GdLF*-zVe=Nb|6|DZPrK}VjFVE86% zDS7fKZb@RvuYr>No7R8L4#dCNLEsvKX8?`w zAzs~o#ca}5f=YDLe$yM319xsWauASWDAQ2m9z(7u$wLiz5U^~Ea<0C8s@Y3R=bX8= z&D1$a=~;A-UZ|DZyu=P>;gp_A)^e1-HGlb)Mc-&B?<<}PM1COpdB>!*CO=%7^pl2~2HCoX0g7N-=kSOArPV!`1O7m1JURJ(zE zEz?j}lX1(W0LfN14@J@4MLfHsJ5pxzXXh^$o~~uYqXCx}6IdGj++veA`d~+q)d}t1 z-Uof9ajot#Hy66;N@5LAM?<7=9|(~zufMD*T5^>tVck#Q6L#Qve}N~9?ws>-S4oIE zNJ?@jK0S&^BF6`cvA;s1iPLUPFtr(^h1OzZ+6}^VB;Q%X8=sbWzVZohAuG?{EfSVU ztBNN6aci<#!KKXU25Yv&z(4NQkZ4Zc*8(LwdYiyTiKIG38;+IAM!aw(`}5Y(UMMx~ z&}{URt)n^J{xtXe7Jr#|z--BPsq9**I9}<1*x)ZIob4p;+Pi{!+AHH^RwgKsVB@E%aYY~Mb zc2>+~OGx7pGV8TCl>1G_wsH@7t`Rx+GSSb8iiTk1(iMYD{2dws+|7SNzBSR15)El^ zF|yWL69;)*6dQ`W4N9PaC<5>Kw*}Il4v`5J3}*vnlBvqlGFf?&b~LK4dEN5!%l+^u z!CtBH=+wZ|{d615G-DvCRuVz8bh*g%U9jTTt?&mm#K9sC|fmF8p1Tz0L16y&S1nBk&=V1w-T#PJzM^z5)!wt2AMve5^8B z7p03XjE(Z8&vl$654DE1O?(|wLM5@7wojjA~88rcl7l;*CV^&Yq5s0HivKRW*l*<^^;7+J;>|bq6z1E zn9C4HY8njSA9vanbk4}nAI73Pg(l1HkhX5vjfcAr%=U>dQ2D&7(GZ6W?;70dUhU#c zf|OjkUN?P?@2yH&1w0oEc$+uU0$=RL9+)w7N*R=K5#QY({QY|#?ana zK=AQKtslvJ@t?36a1Q;>iY1S&7;cP6%(e$kDw%ENN(4ZUd!}L5b0%i2IVXh8(bor` zv*Tyv)1bwfn#Dz^8;ECfSpK>Jksu?DB^9bf9z7GgbY=?{ zxpdI`HbR;{zv=ECO6_+F&+5~4k?H79#J$oCVR(Icj#BkFDbVXU;#lNyOViD|ZAu&F z?d3pVM!h6us!k2F2JR=Wt-&gaJymu$iA6{2kMP7;BCQ;Z=knmH;H+OAdlg%#nV?$+ z@~?0Hhpk^iRC{k8^6d*_c5lS&LCO7Qx;AU;&h81<*_=zX`!Up(Ex;w!DexCvh%o&T z7HS0AL9Ow+jrpVMGI>adB&0>I;^I`Byyh3Fz@b1^mjk{1^&2-)y>-{f>!oE#y|iUX zo_eq=SslG+ln$^ALQhTTU>!dP4kU$xVCcxH*H9mKLVJF_y&s-l3=I5Mr1X7%S{N`W zzK#9M_4ao9^L?O1QZh7D@bB$MM`lH&!P(r6U;|ChlHjM$D{I>;d)(;U+{$k-x0(TL z>O@q1g~vrf;d;1UFgvV6hLfY@E?k^r%^h>Z+NBX)@n+GR8TrD4x96g|pUS5FFs-@B zrBgR5Q#*>DKtB~_ zb>;@Wkz;>yHw_dL*;i3%W?EW)+A_^g*J*?W8#Y^ntHuS&a%pKj9Qy4#T9;|B{d7HV zC&tJrxK{i`9X5`g%GwnM}KxI80DRsVwf=ZF5nN84%k^v>uCp2E2 zwDcneQ?gWQS`jw&9NxlYsRRCyQUXhfT-SbK<)=cfI1N9WG$3815pzRID3u6;Vb6X)<~PtKq$7O#H4?O6#;m@J@NCsg~6+w%F(G?n|JFn5lEnH5YFj zQ?{yW!Cu69Y?Vha7Iafwc@aHJhEjT!e=M=Sbap~$(b#}6rb8JUGPCfHKX}>w3iEvM zM*b}AB$ToNtZj?aj>Bt*oGSOTZB=O=UNh*slG+0U6{ir9{Sh19C!p2qV7uLIZErE7 z@Xh&DYgC|0HqzWDX5ETkEO1EI=k%B=l(-i<+0=SJ?qgWF)8Ntg#8Wj?9~Je>a%OJn zbXGj70;C?LyszU0DR$tb#`nDfR7dg6MNs(imJE+_ELo?%EbT_n)VC6&6TV9PoKE~7^}^};=g7BBcnES>qv zA2E03De*FExYCyQHOPjq2ySrmC0D-=gW`U>u{}g`DYVD|~JE)IHzRQ5vP{b|=fg3P#4%=kC5Z{OWNUQ62M) zwc^poK@LKuvzt7)lC|EPtF3Fm45pkb#Z`j!e4;>vXQrDB2`~3c!T%&mT5IA<~Q<5!~ z-j=H@T?*JP&8QAdFvYK-g}^{_Pc%t`Brf7oBJ!ha9Y~f+6`zsWC^{-scP%!XiY_c%l;G}UMv=;E@N#@d%IH?s%>TJWi|xm)H7P%K!k9tYQkO< zf>(xW2SOOdabD9#gLdwenxA=h9LjE2<&UV57Y&f;qIwc3Z zEG-K5ym#*`0Y9580W}-%rkUX(FYb{cFP4DVkeBwZp2_cCf`$VfFUU4s-6|lzG;Kf8 zHQ}v{;kMeXW$-hr#+qO>;1F`9qneP%hO@S?X1z>0w)_Y7nq@P0i4vNQU2k00jJ-NDE#|Bx1e`XN$x?iYJ{vd|vN z4s&C&B4ZUsjr2p{?<8-9fRnj0#vb2ehla)pJwZgeSzb~V0D{%;N%Wk>-Ju0nl^LV} zJHD0NHWSx?sQ;Eul?{F9ZN1~Z`r821N(Q5^-Go&|L}u?OMj#(_OMV8Q%#xUG1j%^G zeS&th+nW?6axKW+oeGen<~twRJ(n9TunFw5ziw?FwAot&wLKIM$&BDd4#iV}3;cCyGKV9X+|atQt~))}Nt6WfgE zG|r=`{k7#OEl|*EnM3Ff#o1nj{Jb17hk4I~gO|x#akN>P0I_^aYuPi}6wDx^FKe0=5$!6>NfDV^zM~d`VvrX9} z9iVjFVA%k(0T(VuSyodz`kd;AsH=(khvj%NgXDKrbnvjaTpItHxk36Z+doTe=FT6Z zoo{6#>gb@-P_&d8t4N(bv(gpn9+ogpL30G>FY6mDvaaDt+U~I(v2qHO>P3cx7*SumeC)eNT#puCv z*HB714=i~Nw$UkE70h1D?QToU>hium5!K;k@-;QLQ4PV>P&I-Q+ZbfZu8C2G>gZoS zKrCk`W1f8jgc#0O!bjw(Z? zy6f6gP{BYD^; zI}^MX;$5E>C=P|^e%>JJ+lk%sbC7ujKlplyk9k$J(ZvTI#o{(Kl|ex;`zRXd_V~Cz zPYtn3?m1=AZLI!+Guox@rA+~dBT1ka{8jx9K5itxur~=k%=svzuLR_Xb}GcT zuqJG+jX=L5u|tLbz+I8d7^Z~W9gy-At6vJEn*&6Z8uQ(HPb#CXx*Nx zOHF68Zm+6bHfdglu(JHMx(<@6?Z@Yhj_c0c}A-dhesDYvoD zSDr$fC#;^nzj&~sCmK2Djlu

HV}xSkkD2HBBr-;%9Qz_5Tk<|iRC*M#`fd1Ob`Gm#;B#lUb)(v4sf+I%>tc!5=DGqz0 zCtaetck26K5gB%|y{hX9uRFqwDBaHk!ay#+cAJr9&O?E7x!*4D85F-NN#PgSAy2#p zqe!pL%G+>7xoA`D9jb787uW~5C*#yd7 z^IgiJ#W#~mThGfv7E7aBvDU5ucvhJ=KQxYzBinBvHe5Jl(q*o$#OO3#d-#E8vp_vv zu5qXkV9rHPr;Fg{fznp!X}FkoC@ z5__v}a32Q;M1h{%9OKBqmi7vv3zl&RyPU`x8vb4aLV^SZ__4Fmu>apodae#PME+*4 zC4s;t>o(RU?CCq5I3vetS_-#a>dn!dL?Z_q$FVQs7kU_e&EXNemovzEMfuL@Wd-*6 z=W5<1XP4R33kM~CfI`uHZ?f@LyVRx(Lvl73qDwPT7LKgmNgadf^5EZAcK8bY%=|1i zMwZeFN9zWZ{JJLTB&kbTS7>yRxlM=PmEC(SiQKxU(K&{Zk@? zRO(W6=bl5g6p@c#L%oNf9TG@oS2RSUet3;{cf#w-DEZL>e^uwur=b$*Wi)afTEe$5 z_wUeP^hZ;DoX*&Ws9(WgcObi=?bYBT%UpBsQ-YH7%4f>79( zpq*UIY|ura%`N?@7r66+$>|!;+6P4A77!KIWS6#Z`6qe zc|~ODX8)`$U0nF;_Xv2secXe4_L0xg)u;uKmo+&FZwk}Ta?UcfrUfL3fVUUW9sTS- zOPR|Oqla=T3?pytRni-F$#e+DYcc?{&nl+08iU<3vI(@uGKq6&sjf2y6*9Uy*@~r? zne3&%QJ-tSu##Dp+0fvpc(7!*qE%;v8t<`@(s;^cF8$CBG#H~1nrU(9~zulpk zCB;cP&NEvYCb?VZK4ioNxJ@@3?S|ta<3JA15u0*pI=GfK75d=M`Sq`AwxHdk(j6gc zBVC*^WpDWT9oZbBDrdm)v_z8|prMSxK}!JLU8LqxgCCPjJ|&|5)Z$MxN`AL4N|y7n zGzV?1?zvDZUZhUKxdE1>nq4FKd(PE<^v$c*J6#w*k1b?nx6Fp~rZjQ?+^2ga=MZ)Y zqgJ!eP}@hd!VkD@?!U7rXCZTVgJf`xl-BIKO5f2#)V_W94XSt}SK0EpH?7I&yr?_L zozB}rIv~qGQx%s4lu@Xt#N<+Jq@=g|3GT7yn{q!2c!+T=@|`sDPXN)56o4=}YumTX z^0c|Xi@z;&83NG-P5scCtq5AO_ThU`%nGDrnQ}47rKMyv_mhDy==U%nMjTQ<-@J`< zx_0I8CwHUWtnk0wU+qGm36Kl!zEv6|Fw9zp>I&FLCsCMEH(xXEI0?H>PKeqP7IMsV|9Qg~c> z@_vM$b)s3SyWMJdj@+D>sEcJy4H4{E#Iw5gEc!@=7t!*AUhJN0!} z`6JFENzK9Sn^9pMeFE={K;adwt#74bDZhhnclteD)_!@Jd;si(@d~>v3_LhcB0H&) z55-|xaf<>NA!laarSflfMov64$Zx!%G_Y_IGv3BY~$#8!=q zmx=rKD>$h+7g@)&gHBdNgUXlOM8lzjZm*FCO{mi`rDT+?`|bXvR4N8msP#x+Wpw>z zA5s1%u%p8N(j`&6eTvJ=^fzB0HW1GnCaN97>G6boRO542-;KyVbbLx0a~eZsU))_% zqizY$=!L|{dihBwnjN5}{d*-9%au~+rpl1ITVa;2M{U0;10L6a2t_cxFw}-MN^3A5DdF*Y9&6|y zS`-&THm9a4u^<>C1vhVPL+!=0*$~mK6)Bz$oKw##T~JU4FLW!g1^)KAfgX_CycXJk zEkd${ltL7eVPO*Qoz75|e3u$1FqFM2qQ;6e%9c}P20DN3V$-vRR>6vf7j zs#UaGcG?9x?gI)zNokO*vZ`|pUo_u21z}Cuff*fDr){pJ-g7Zy2P>TsaH$fAXG9#+PsmDro6^}2UxAdQ`5ZQ9GUK%EYMPI0Mh!&HCp zW=@`vDSwB(H}wKNyV$K2mSiVhvu?mGy~^r_KQqg;=FRBM4SD|i$1-BJ=(p24(1l6U z#72^BW5y5FO%Yc_RgCpzbMom_wcWF4t#joKZNZFa|E2BD(&l({eb1^_Vp&!s!d7gp z<)-6MOGiDMo|$|?k-8aoPs9akPt6DFbJo0W)`=)VB5jlSC?S*{`>r{aee2j$bVXvJ zygn7D!APtulDABB3tXpkC;ZxN2soCoMQW9dc_tIKkb=(9q}s(Z>BPzHUHCAQ4X03j zqq%AL$~opY%-T>Mo)wZ#-6hi|r+z1FinOI&?*ttkbeS_e7T}^YV}IGK3Rztu)*;sV zTqI&1srGIZ1h8@!&)6q+QS$LK&3?D!=1bW7wmtGo z*9OV;yM)(GoC4|fyCWj$KVm!9$S?f+-7ow&PCLqj-YxY1B~ReALue}TuaW){*_spJ zyQBD)YYKb+Zu(c(*w&kW_vJf?`XO^hv}pik9U+})nczEDnK6U!CB`iFdanK|3>wya z7EgSS<);<0LLl-Nax~-8)NjegSd3BMN9QKVXlkdwjq79uJ^4EY%vcD%9cbcaYQOO@ z&seKtfyQaL)b}&PIGK@#*w0o1O0<(`m_Il^*ESwGsAzD^IZf>YR0aAGjI-|dF{elh z*a-&==g1FVkiEh~W5C=!O=4rIU<{bQqmQ@bGAd*pxqR+v-B+Z|*yJq$w+41`(8Lcg z1>3eJ6lm12}z75djrn?dyr^VXH9P+zLI17?^&+Jp?a8iTJ zC%rgs7i{Wu*HEE`rehLH;Z{$pp9(q*v|q&Vv=8SLTf*;CfmE|=Pft#ooXZmFMGs=a zw;{u>oI(_dLwR#pgyBViTwnrCG8;mn!=*++`8>s=Ct@3iS~bY}x}s&%sn~Ff%SXW> zPvxqhWG1!ETZYn%ZVz1-zI6`)zTf5tjuq_4b`uXxOsZ7SzTg;pHC|V3G(BZ`DgVQ- zlVriB1!<=On;Deyp&+{Rbjjq)VAdB&v!H(9urwWlT>-W>ZF$bGSdHCO$k`ej9@C(A zNYTEj35$-1Yc8(-`xC=r4UV#f^8?!;EcG&<&SUM$D+FD`K>ZTq&S*EISO?pH>a4D` z5MJOqQz_fWjscmH@ERebvnZO!8s$eL%;MKI6DkL;ru>CPiNS{&A?l#)a72s zeVl21^g_$SXZ^lba$_}MSz4DJ>?!9vwZr=Ov$jN+gA=m3*>UuD>;FLD(-i=o}1MWeO$qsB#mp&MKeNtPhh4|6d@Nh=1w}*y|Ct8Tjhv51ESvuP& zkB6mikO1Xt_r~z+8LpLyO=?|;90(BVKDido)(`uVVkdNpP=n0iF*IM+jI?;1ae|xe z(}!JV6XsFsJ~8M|oWk-5czb<}HLRM=gkOHkH?Mlzyeu-nSO$pgS;N>rPAr8QE0+bFQ~reo>_ zgQu&*57Z{y4R@C-$Mhhly_D%&itd!JmziA>cohwUQ`;%^&4n_nNBceBr`HwFzDBpL z9zcpk*};uHpm)QXP8i1HTX2QPK3+e)ieFe-kWQDgmeLE%{raw&sdvHKYCZjgqhFQ; z;|608ACM{ow)s_@-b4v1v0ClkBs}#6BJy{t=5;d#<2&-jBx@&7%)4IrN!s33x~m@j zam5Ncz^A87a~-mwWuTV_EwfMf9>s_P7dI>QtuH5jYIlpEqu55svwzwMwwr{DzoWf# z2jB^nu>+mB?XOzs!+k=ot?azgqq>Ln!%RADa`~!W#-)9BPRf`6^P&u1ZUlBHQW73d zL48MQ<5=-&(kalZ4PvC2B{jQQ$!ZQCeUJKnQkVzKtGRSFLBX4P!sPjgc_G|+v!4+G)fdXkf zG0D;?xxAo1-jlOAyhf<3LNp@DAG;^(fVoe*^jS!Vleg03o~$??Ra}G{^T69tw||=b zaRsDeG$C)z!KkxkClsuR3(!sElufs{9fFH()zu+c(aKad(i7qBgXv(exkBv2HN zY~c)B&zviz=8sy=Cs8lSBI5tRZ*J@S+!vWZ(!SN#+8N<;YJ+N^C(KHYq=Ecf!Ho+4 zwz`;GaAV=oYSxfq8 z|EW%0P+3}NG<^M56(L(^8omZn$=9h;@Og-eB`iHp!yMM=Uh~UXO+0&&PYRPON6OJ? zWIKa{Y*}uhkoUkp@O01u}B5Zsk;!ubFtA~ zKGs9J-t8YR3D)TAcema-2MW&<{ENT(IZu>&>k=rOFRDutgbQ~QX~cj$2p{6b+e_a6 zO=+FkF)+vAaz#5liA3)~1fu=@r5jMIh7<}gU%V9Pc%v|s^s9Lm{x$s%gT;{f)@Mj{ z>IRkuiTCWZ>wfa?%oFSS)#SF){S}(h*!zF+SAVI13Jbg%qV_g&_Skg0sKz4hWlS${ zo=Sa9c>4XPYCEofr36428Wnklzu}FMG9tNH^NGE9MW@x>0MY@YZ(uVyv_qoInjf}$ zbOZ^c!QTKDAZ&f2W>_>=@@jrCdYT;PIA&=sRP7`nz zG0T>Oc%t+W5xJN&h2H9@WW~sKze9Vpsf^3U8h$a5aq{HrQ*{kv8`C$KRV+2Iwp8S% zEchQ>R-r2l`N6-*EIV?Fpi2-akS4UCXr2V`KwIor6v-oLx%5I0}P_98%I2dKWHDu2hLxj8RWgxkI z`YS*wzBHx>NIbqONn{TU%W_@HJ!u{tozD^%f`syn13|FtUBU6hE0`nlhb_vjYnr%a z70cG>1_(;k1#62FC*QNb;Dx#&we(m{?q7kAqlFzxNdrQHuU|ZWHfnwoe6a-kevHhm zNcL}BBh*ienXSWHa!7g?#-i^%WADTUep z2Zl8O^o?O<<_1vUPvWY@MvcRg+<6rpuP8k+6-p3fr1Ei&3Hq1#Nc7gH%`K{QkZ-r+ zwTmD4w|tI8L-Gir+@Z`7b)lUFBGW^@yva!g42e=IA~ic>r+9t(sV_Q^GCX?9hVV~k zPK!)1Wy)a4uljd;yTq>84D19?=l)7GPZMe_`z$td1!WOYXRt7Ye_QFR_XqHeV^DxA z@8}If%WFlmut_zgY~U>N(t#-g$!S6Qt@xpszs`DvJK(qC$m@wjhpd#r1Yoxt(&Fmo zsIPm=XIT_~nTI}$wmB?uuqi~QU}D?i!%UKI}gX^;k_-EciS>BvH=|X2acJb=US`~PtZk+1!yfelDkf@4{fp0~^?CuE#KN*k5Zi(Y z*SM1Ia~toCUn&pu&(C&vY!GLJ^3px;K#ISUi&a&71sUGqYz%Qj`i|%5NqGrj0j$F9we)Td zjR@otr4YoN+I_)6Bu9It(;Cm{LJUJ%67vYIEyG1a{=GU8Aq<5!M6%G@#wq57wyC%` zR3Yqq-I?AtJ&9Gdfr&w>Wz^1MX7I`DSy-(!AgwU-G)gTxKv@*pwUx5d4w?1mo?ac4 z#;H|1KuRm`rup4;3filSx3DW+crin12u&4x8Q% z=^ydM>fmW{l`eV1%v*?F!vc1M7c=vBg~fXYj+^wb_zG`-A}vMl?{@F+2(tyk1{K`0 zWLkEz^EKY@K`C?P63~0rW;9xo`9f2tG;VG9y*2jP5_L-0A&sfiug&r_Szb0{E7xFL z{b=B@EL0MA8EJ2_+|2U~duuw?XSbcRbfCjI`YrZeVG%j(Le5rQUDeaD*JSte+^)3( zK6s@~oYZv6%Bc0k9e#jadqP{@uw4&U<@4!wvhcUQdujhl*5d*fa+(901$8QZNE71i#$x z&}E(rXiMqYZAt1_f|b~6;R$jVHJ{DOyZKW=<=+EicfJB8=I(a?jR5_J1NyJWyZC>& zAH+**woXJo0{H7{4ESpouUu=^*_$of*_)r1*&E%K=d`vTTS#m7X92^iWi=^(#Hgws zkP2ei==`i{-n!MyjmEw@d_18fEGyE)814?{yys!p4$v^SY9)o6#{WMwP>?0+I)?uL z0SwgAPjA9jH8B$}nOs+RER*Ip)bI62 zJ)d8Kxl8<&3hsm*s#2md0eN??W?LGM5v)=5P{;Y(UF|$}xAGHoutnBGtm(j&gohiP7Xy;bf|2hNGxrWof!{9=eqqf=AklG zh3*L&1%RvR0Ul7nwrFUb8c2VC4-kpV{$FE&JpZv)Ji*R$8_TXHsb&_O<;p@f4V8aj zx3I) zpvj3ubC?WE*e?TL-Urhc$s@fy&rP zGtZ#`gzahhDvR<&1;@J4{^r+b)v*(J9a!7_GlwBH^DaDsXl0*s5>|9w{y{v}-#kwi+HAy&?ReUke?C4QLsqcQIpbbKWhm zs0W^xkqS)}i)Mvw+-_030{4?aWm>?tqnh`3dLxC}@!!LHvi=pff2g)Pd4=;*h;MWv zP#v4*>5bzcq-@aNk66k%c}U3~9fv8DK=06xR8p%HRP2`~>t^%!Z^GY&7ETfLU(QZ- zNZZlKmpOxz3d(BM`lBHg#E>YEugJ8a+U1oBxZ0;-<|F&f-N2}(Ju+rAn#H97i|Jih zKLnmtMX{iu*z({)@v@rx@M{0kLYSqG9c&_1`)_fk3K&1ZMW3c46^nQ7s>vAHDuGOV zNRcyMPJN|7#nSV~q9HjaNbkls!`-W2u-_{f0pblVUBU9=|9+#{$6D$3a%C~q_Y!Gh zQItLZ{R(xpuS1KE^KoQijJ+}C z`W@)BU+h-bZW~jl7?tDo%we8#U!6`Q2b@QK=KVPIDTbeksyo(oD%t*KfqIvB((w#g z!$I;wfScIW|Ju`4aL~1=ojTfgHxje9rVJK@)leSo=~dlV4(Ab?6a) zNCdb&9gnhtYM_&noLo?VxN&>wk8X93rxs%qGqbMJo0EuuNmd|+*8Vr_XpXNPCs%UQ zV_=3ZH?J6x?y{%e5c_mCD?*iRQWc^9OXOmRX!DqlofRkzc}3=NlHT|yq3#UW;a_be zW&I2~h1g}PT@wv-1O?d`IB7ny?K_miCqPAE(O}HE@3Em%@A~&&5#?V1K7r43Gd-Sz z9Z29EyN^Rl^mUVk%g(b$709tZIvKH3f0~;IZE5}0nSud3Bt5n+bM z7t{vAqo(_?$&TTeGqXn0XAag^eon7l;n^u20L1CH(E2O($iGjqw!~m)eA#E`IxciJ zD~WGWVSBxocKxYVxMUYxE+Qy)Aq5%0lZ-Cr+txOq%6#K!?v9a((mD2s6P<0Ex$c+F zI*_^f@cRr8ORTVu`|}k>VsJ=}HPfHn$#RSDS@*PmT-Net!;&S=HV_BW9zK!JIO!y$ z+JS>VwCaB(RQuaG%P_fL2HG3AuUcS}!AhLt>JB*e%8SpDWSha75)Zt^6zPjJq=JjU zOQhP(B{C5eT_p1UU-IX_p`Rb9hhO%8xSfFipgwIKLz4ITKh^8yf&WlKvj0JQyk9b~ zEUrl2`XJ;7CGRi)n+j6>>56^Yjo@US>#X@1_Ww{t|MTt6W9*SCQ|b3$F*QQKeP=0| zaLt9)<6*@sC^~Up~y-JO*OOL9%0;Uti;JMcxqzu1@znXfS$mokK@DR?(TF+QqSkx=`N$v%ZMMIkIzHL z=k9!A;ZL-WpZnM5xlhNBPLNQ}<$HREUtFl~Bw zG}6Sr-G=&i0Xn4N3$Ex$#zF{dFE2zkR?xHJx!+|cFlNDH7)7)*E$f3YF0- z!B`u6lTQIN{U{h~55)*p0n$w%OVLMvVc&+M?lU+{49bf8@d`{87x+J;ARO{;MeO)u_iJK~2 z{dtE&d{N|eTIlR%`EB4}dY$W|xJ|cvi(K;~Lpf}2o(Cu~j;s#!HrV9U!X9rWlkA!z z3z6EU_ZZl3^VhDHi*(EKm~?phZg~FQbl6bMVfs?F=W^;MW8I`5ADfsmuOG`N=icQk zn2<<5FyowBJE3vr3|$l|@+Vo;z4r1$xB{0iyg5UOdDfKmJF5 z()4b*7kDuA@1gRk;X@9ZSF*%Y7*Q0fF#-!oKR#(_I= z8Gun`Q6MuB`h((NV9(daSG|ondRB#Yq;HD4ROIy1aT*4P%15HQA3=PA1jXR*RjX3j zcM@4_(yHZWaa_qlJ6iE?G0%AH1*N-KTDw9YAJ;)y4UJT+c7w10GguNO1UzCXAe`cZ`TfR~*BR5${`C0Tf{czM-{1eJwUe}JN`u}A~;ysYHYy@pKGPA*e9aoLOMAE0R3 zLe6IIYBM9Lt@7RJ2PnGFP)bdZsw;>;e``+=U&M2PTq({5g`a;G4~+!AzHs}MMTC)V zXlqEJi$rU#$U?NNl!dNGo z&2ws}1>i0ch{6g!AHs?Sqfy`M5-F^E&AhbKnRYQ7;+C~O;XyyHd#$OtK%Ui;a*@~% z!ld9>2PVB7%vRao>)=IVr%=KRZrDsH`*dEqN}ma9)JO{25tkH*>G5P$JvxTlA`A`G zQ)0YN+1teXPYCyb91c5RKkRkymUgjV!0CHp23$7b~4R(u4IUmixNwSDWP2u%R z(I-r#w4qmapJLg##5R7NaxVyohjl*(2fyQ6w|-rp-cJ=R9Sq)VQ#2KkpdiXSN;W%B zJH4=|O}$*6FdsV1%RF_vB%Z7MA$_Ss#4B(*?3KkK#6Xf-^34ZP!Nl(gw}z#D&S=AJ zys3Z@mlb}}!bNE$0DlaDX?1Z@tcSalmBTWlZyTC^S-VVGJH+E^)tmf~o;&J?CPLRM zH%nb9F@>;@6lS0yYyvBzp!0gkG^>v zbnR-wIlK&By4WbFfw6k5_ilD+B+>nl)Bctv`AI;hBfmAke| zka_jApg>x(ZlSvzXzDiS9NpidT78;3gR+`UCg58VX^E}Aj#+sM&X!f9+5{R{bBv*A zb;$~-6VdwtAB|&>8t0#B6gX|PapnaJ8Br&~6pynqZlX>2rie|=YY9Lhv@3KL41?YI zJ9pYFSi2azoV8q|bMQaODWMBqG9{hjv^hGJ(>VKWUPU-L+-qrvi^JmB zKdwvbJGBHi2x%*k`dIbKjibH2mRo;v{Lnh>`UW&l?V)*3y=o@%xk|@+>Tf!K@7z~k3av*iFVkH<7J1~{;vZTy3(5+A59)Dbm~_S7T_j) zLs{H*j7`7`|8 zchJW#5&dTzp~JS@Ppj9#>th7xkG0SA|MdTi|LOei8ThY%k)}uqKs~1gWg51{#_o&e1@^RvkO0ZKPwZdRseI+-N;`QQ zEdB&w5qE{%U4;{ z;h3Uti_icw*kit1_$jm-IA0dnFDLI)Leb-tg>u?#s%9>K?f+tiIv(MjW5gRwbq**% z{gAdEzj!bXUk10_10&L1ZrakX%n}2)Nv04R(jY5El?bm5y!nhkRSC|nt5?Dxm)vVo zC5s%Kfd>+lm8!0E{Yw#SIr5>uzu7*pgeZ}?BNkHFr#3JRCX}c%nk{HOAW=~u?T19s zrT#4URIh&1sNAQ3cYy*WR?928Ua2omm-mT!D(HUG2YWS2Ta2bte;!`sBk2Kd$9pUROoQi~F0)DaW zb4&OweuV4Pf$(G=EKbi}^+BR4LpMnPT*3S-j|FzEibZt24}0@5zV4yt?5U~>EK68ohqs{*;F-W|5}shs8p|5u@#@+ zx<}JeSboHKuA6nR4WAQF)3VCnH5jNIJ#EmNFVCFcd+PEgO*_NAZMQI~GMK$T$O88F5h%ARLNk&E|oJ`jZAE?(y$ieASbW&?92{G6D_8mYbJc)0v1 zzb@ZwV_EXU#6~%DWBbOskEF8)qT+DI7~}ZO6O(jdM@qgDNm(vYRm0JKL-E3a2cXPf z$hacr%B_j;!aq^nDYB)L@;aGuQGp@?<&_*|pIT|GiQAhaJGjQhr($a4PX6SQJd4`| zyqT?i>>;vuqpzNg2>xU8PoTt}s>PT34!wjvbt_v%s(t4UhNN|whT_4Q{5c&SJB^x` z;GX7M6<>1BpsTB8KfWwBxT{0{4^Jc~)7tD)TOT9^bc5hL(H7F!A0?A&Lv)a13uR+W z`wv?as1B*6ppsT0N&T4>DI!`K+c+ub8WeRp-Ihm1t?3>z$CvL&a$<8AvDMbF zzxk6u97*o=AQ-TlRgx}{0!2H$VaxluoO=e=ysG-61{A{(hazK5pJ*ua?Te!uRkISg z#VmQVhi;R&s-T&O3&722$z*aE!KGf3F?W=}MNkJ3)*1E_cz3@=@E$CaVUSU6y0=S_ zK1DP`_NHU)4TiI>%L-ckp^0qeOwrC&Ti8+qqY&Sfm8>yt?Ji1v%w^oZLc_YJjeX0#zr;`Qn*OnvULZV$&&nW7TTATt<9&=MWf{Hm zwpz`8;%rgd_g*Z^14Y{yN8f&Anu=x~EgFC426X)@7jCU_MLU%^aR|d{&kNiO?1(ix=*aJQfyky6{J8$U z7iE&rROvf_R7{@>6d08W4b$9~E1s+0g!8$ZQdfL@qNY*o%V${*pg%?a0EG#>6Q)KXAbyx-EdET!10ARe!;{w!PN z&ZiijhbE$9!`#73;EXI7@Zd>JaZt68MnbS;l(U?h2hz5Bt(I8UqBy`9*R0Pqp)ejI zt9|L<#4fx5={^337nto29sPUJailg(Ve?MA^Q`ng-+ptuF$SAF6!oVnwZ?Dw4nX~B zm8mE069Q!9mIgJ+hyQ6-Mc$XXHI4j3s==5RYr$pK`skYC?&Q7=4`qwB6ACZ)y4m%CBH$%H z>dWHp*|;Ed3A&MoTLw;2QRyy3*Z)3qsagIfT4^$hpI*6pqn*6j@;8Qda{dXyXd09$ z2b&Vx9`>p7C@}CMwIauukKZ4Oz8fkm0`P%CeJrooz+DBVi=0AzcQm-TKm$dlXlJ#t z!{u~dB+k=rhwI17wiIm!Vt&l;BcPgerl_v8OMji_YnqA!$GRoTU`7C~yW1(rFWs@j ztk00*W;6fAqGn2cD;b+OpWeWYFbb|+Lt;XfyJYIm813>;>24c=>*zx-^dS8Ew#yPb z<`$P&oTI99BbyVfbb;R5=D~Fn^5e$4q1(D5(KsM4Zg}fu(2=pqnIiej$Uv7Sc4UN~ z{cerIB&nz0<;95f2?`9Hehm%W7$SO}`bYliN5r^3D{kaf5$9d1mJ9}4WfY2T%xE!p z|04jS#j(yEy1UF8{-DUo#9$^|0W`yw17RNe2?hdC$s)mDjJfmi#0^Al#b8~Wr5*40 zxfj)x8PEP1+%t4|0sgxDIEc~q^$>eH40Y!yyca;&1`|OzNY(cc=^NO{+;Ns3&usRV zBYXxKeLD+wY`X>-Q#a662MU`{kHkYpy+Y5e58LR{GVL)xKIZ$}$ZYAE7T$xh4RV)b z2E=dE)PE6PV4z%=So=pE1Nh(dHy~u|NcncJAZDchA@zWExgXRSY~o0vm!(YcxgNVk zq4`dGo_LsBd9(>lJM3x!?vRSjMSp?;0lHX5tBXZTg{-!AWHR$$mHtU?W3-L{7Ig{8 z-FFBEUV)pVovF~O5+fvqR$H%y?stMQU9=_x+@@;1tv|oPfct~FwF4={hJ)+(#IU!_ zb(^m2ZF;Gh5c%{M^cSO5*OuS?Sr+C&0Udq2PsK_>m3);vtBBQC0y_+h#)czu1POF* zYMP%+Kumm{6@!lD>WWJb@T#p*wOKBSXHcU78*vp%yTdaVt*Gr#FY^%RUEQlB_xrBLmK?ymx1Lv~i(bHjHLphha_FcgvHQcKQQk}~1JwX-Zqa#V?9tj3- zlQge?k4y2W?X2EDA}jc)>E=>R*+go4%1L5cDyQ5OQ#*j!Q1;o!52v!oOUVo=W#6;W~|dy=HK>w z`V$p5f|CI>b)MxbK-}Vj)mWt+>PQO&^i+JwKIJIUqBzWlLvrI12(a5v*GHQ(kK2hDy^aHD{^eyq-8fjF2 z`{zx^oad(4U0uoapvXl7OELMKAS8y3^5cZ92x)~(T{9tt9!CQyu*t1W-JnG*ntOjE ztky6GQjQ@DbQC6g7(*GN0QtnG36Du~Cr676PA5r>ab+a)NrFe`n1DguSbv2ICW{_ka(65d>hLC=G(tG?nysm1cJ3xj%}iZ~6HWLeuv6{Q@FC?J0k zPt>s$rh-Tx@Q4P9cj-zm?Z20AI1Va9(=Hq@VFr2Jf06BR|DC;+&MNR(tfAW-=^AJ9 zM$mm1{vxoJGVmQ8_|K}*$o3tF> zTHYDM03DVd_Ea=&a%>mIq)~#}{EtN4R9f*g_})0qSrYs*7e3w{zSWCi5~<{>4t+a9 z5=CmE51)7J({1#z`2qLZNa1wN#*|ZI-1@OOpb|N;VPukx7)jE$X;LlrHWxn7fCk7IzKcgMOD&Q+%kfU06q`>w z*HIw~5}yS5j(d{HPeJtM9h$bP09`w+0L}G3vq%Zuy?P`AEuBD?0gF+xtVbqzqwro+;U;H9j5+Sapx)(>uP>Ohxds>r4A5DsEfr zSDJlva|*%Zf?P^N)iH#t*cj8+*0C5@waavVjPY!17Rg<~wSd7Ast|v6Q-?n5s2|x~ zpC?v(uGZ5Q#^xeAXm>E@qtr$s3#zje)45MhOW8U+6p7vvIqH?5SxXxHf-Ld@MeG}l z6vrBg?^8%N>IhF|-o~~OD7b(x%xGzW1XkrXm%;)A-2a4VQ5svL) z<4nrQb>9Hu6^)E>d709IaYK=jF0dh?+W97(dfik4b@qJu*7Ux?6%rSj;i=h zv(}6ztN!eAtt#k~w~!=bmWjRvlC#!r#At!!q);0oMEA|7Jr&Rq(oSVz2D3a3@2(crUI zN|iensy#mU#X_1U3Ie2NJXs3##Aimp5twQPbeU!g&kP`YdCIG~Os&IdxsPRUEbn)z zz2_A17rU|%voKPTDt@ttrdPktP(Vy4j}I|m?ep*b32F22JZI{JNa^I{1M_@pgo)}4 znTtl4AzVrZHNWzE*9jI0%kRLC=nj*NuOq{0^dv)ZL8jIMgZ_)@D+>?Ocz|*sFo)Z& z9ty>Rmyjy@mvg3KmxpUzhXyaNWyec2O*#_>7$}HQjTek_=5Sgh6dP#QNcrzyPiH2) zRueqX?M-kH&cE>n5V9NNe{f*}yDGv$0>Hv-Bt9Jbh%Lh{c+WSB37PLIqT%Zz=Tyx{ zBN6ad@uR-wox@d7JdsI6qgBp`M8H<;G0FCSy#rM~LX$Qx_%;h}612OrGnCm857vl_(3pkX(1;O|R}DoF85l|`K56)hAdXEHAp zCR^qfCVM6;#wl8cCR^@9AV3qh;h4X1izRd-cH8vyr^Jv#2TFd}e&Yp@4R&;X(S=wW zmUQby7`CYBZzFD)t1ODC!7;z2oE~^twT2qj>L16wCC?P`Ks6MqS;K>qninCkEj?o# z@$0b`owgHnap_1mVBP6*yA?g2SA}K7C?x6I0HOUo4}(=#b2i{H8!<@w<#Vi6cx5be zq4VsD$$db>5L}-0yz=2Es;W0rmswm_b)#o!*z=R$kGza`+Eo3ki3Oz z8eb2d^}hA7_`?h@+-b`{qQX2S)e4NY0Te$rd>9hXR|Hk_cU`bOPjtm~nMCy+?rS8n z=bx+>l`w|`!~HOQan5aP4YO9&O3nDdNF^udeS~=4rSyao0K5#+SEhxgqO3$}UdI_b ztdqix>)?u9TR3FkQ_f~!CBp(8=X_%NJ50e$O|<38#_Db;WCv~jjyED5p^LsZh;Lbp*$ zob9EoKN@vK36(t)x|Mc2F0C7TA;Asf!fPZAVKf8+s-ByS*@;RLd1{p9qbYMx|zBwXHL|Fv#Nu)>#EoY7o zf45}5d>dQ#of=yRFE7iE&F!xF83o&IAF z_|0c34?QWyN-h8nEvw2f_%RPI{h|VEnpG>&-C_O4*`@Oq~3*~Fnt`2_B z9xX7tm>I<>L-%+F?(xM%G17TrW;XiI{glF`MDDYne*s04^KPD^4)~K47~cLdzpjv3 zjICdGa+EW5Okm{&ien;SD)hU=YQG=Zg|;%tU_BI<9O#9oj33b*4srl+X6Q2T>osz= ze$P-_Xh?0nO6TS}(|7OOHO3x?jlL0h6d@FTOX+HW_2|;DFLQ+UK-Nmro}VG*q{!xA zKhv!?w6PIXcB;V?UV(Nk*?0;=T*Og_N*32AfU3RgjkjV$XQb0s4Aa{{4i0NOgc0#!Rwk z$W_3Ibz-5wYSr*8e3lzXt9X=bt1`pUVFvkrazndqCv)C%0-026Gr2xnP4&*ut#Vc0 zEY3}_jmua~hqk9e+xw~m=KLwfUBU%!)lR?vx*12*F7%Jb>wzyj6wxZb44P>y9199g zIrd(t2}?m^Y$|z5e)wHZO@?)0MJ=V_cC+Jp^`#&thaEO~le_kKY{~kmIU>dhn%>qY zgO>=W_v@{jK`Yb;lbHOR@1U8;c(}~zj9k~euffPziqa`%C?XUbR6#!l({8F@C|pEs zg&%fCfzPgHLQ16+I?g{lh^rRPgS|Hvk6&~b6)tl;e$jiy}j-~hsM)t>}W4 zP{=gV2k5L#(GkipT)xQsEX`Icb8s$zLF3Hejh+rPMoeQ}1&6GWuG@Wl*COe3IviCI zKFuP@x&rM3mYWZY@kpIcFJ9ZNmEp_cjMmqP^lkA$`A1Gi5vqPY<@m44SA5;5Do5X@ zC_B4EA6;#|Os6|{@b_CDY+BllR#mNnzV3Gc?h*oOc)f=o1=Vh^Pl49!dCxHgtP_3- zgB>ckzd40WOi!roilqEV*V`NF-=e(?PD(!Y`vs?5744@^23Ir?m}%CnTX`T+E;{N1 zm|1uvs+Da<@YFxvLCYdD6OVUn!0b<+jP`3REPNl|Ltx*LUh{W6(89P%r;{IbxhW%k z=bw!`NTCG{bk2qN`)SHOdRHe9^fLIc^QelBPy2z)J0Ex2_L0PLttlYvf&7RWB9Rxu z0l1gG9JpwLn+vOm!-r=g<$v2jFgiTL_aTx<74z90Ljz_&qg@N#+_-xqUAdK*d`4H> z2_7ZM{$Mv7k*7SVcg0ORG^iHhCmg=~tT%en1H*&F(CZmbJJu~g63u$4G603l&_5C zhK0n}9CCY}OwSW}$7AxWbmK!a6by3ABFnuqScV=3i-8R6D3BCCFY-;D&U%>t$* zwU#TXOARp!Z?`^S1+tx}*S(0GX+Cy)k{Hc;K3g72)6JJp#Q>%?Tgjc{zW1qh&O;s4 zuhLsqCZtZfUMbIOk>!h;#da6eudFH+6_uR3c$F4Y$N2&?DoYhJR!0Z<>l5hyzN{@! zZ<8C8e|EV)W8If?3!~ixjeB@S_O!?AtJdI}N^;dAs&+*Fw&-qf{s@2Lt#V*P{#)FY zQo9@+sbwuEPP*InO>0Bq)<^@>*dKLqA4#-tR4v+~~T5 ziAgc8`i;QQ5xCTDgQdYyUsXp!&{f!3a3A@Y_MDq5c(zBQI$v2{!Ha>k9;@Fb!}4>4 z#|(TrtjsFxlBya?S?kyBtr(ec?Uz%m<}l&C>s1z*=vDHu#R_LQKAVgl;%SoYlgD_n%Ip@ADexxBa09Ki_Z8cS1dx@BJUW(4$)WeVD+80tptVZVLrk+&E0c9-58#< z#)Fqa177o|yEW~6aPn4RvW}J1ns8qE1WKi7E+_3MtfwUZq8q5}^P*By=?AFOO1XQx zirS(BtFHV>gPNjC-pF-N3w`&uFGqXnnNV(*Vg({&xg}(}65T>|BTEJJqXJSQbZTie z8304?B`@`msK)a=UwUJCYz@ztZDWFS-CcqBYYgf0w`@gT%qt%?j;Za~=K$2;&9*PL zMw`JbZbjy>wBy52fo>4eDk)k#8m*=z@6cD_p(11p`nq_6#1oti;t`a+_CI9zV4mYW zq9IP1MMZZ&H9Pn=WPUXmorBD{-HG@GJ*_s z7TYt*u24xV+nCk?y)vrG42+2XL6<%L`&vExz<{bEKNzf8u^3Ksvw(zN?UNw%@)l`* zaY2zv6S0+tnSdYlgd~Zgu{BY!Lzm%X@C5v%`CvY40U{%vSiQq*6d}b1rPYvJDUu#Bm566gDVgHRHoGCUOEdp z$LNOYJ)PB{;L0K@j3B!p)vN-281v(Vse%@@Jg-cUI#rdm@wtukpu+)iT5s0!(mlk^ zp&I1EsFLTdI{=INOCHNi=Kr zD3~z>$7k*xR9YC=J=V4-uj_cEECbh=bOSgHLddc8XLvk$Dw zaMF48E|DLW!=WdFk~lyOxo{ZuN-!9o@!}om2i7o)AyJ1*h0(kBzL|=jOoaI&t@agW z*ZMd7aSz9oWE0~va4{7`UX0sl1E*t?%3Cf31!QYv^;e}*q1mj|wSt9&tKkXdc`wff z55asDwZHyKg)ImbOtYqDiI+6(Uxkn8@e~ph6RFT#!^j_5sHz94=5cfd$H{iV-VCtZ zDT`PEHVNm+mX3grdqmTrBh!oh0_eFTU`A{|7 z)=Dvp1bhbyMI_yN7kYNo5c2ZqyJ!0tM>h1%U{vUFtu(6f82))YAr}k)I}B-nAsMs% zfniIn^@A_D=Q)TKTeTgcJKi8@1CzAJ2o4hUB{vhmp{A_p0?kll(uKWgZD(e)!%fa* z0C;i7L@ebW_V?4f9G)9~LjOmIe9?>I&SCpEP7K_?BsW8*>knpoW;~2?WWa>K5H#(C z*x0rE>fBi#ZC5E3A&ckJu+tsN78$yPu^r#<)cw~&>;1n_ge2Z74TeWJ`e~?I`;B$Z zpzX+rcW0&tyv)C-!Qip|8V@4O8##kPW z81(d`@1DC<^uf8+w*-~`_PF*P$ZXXYZunGW>JPSsduL|Zi_z%YS`Ru(0O&{GrZDcZ z_@CFBVVuV_q#jyNt4d79sjjxC5}gb6Dzq;6U;JX2EV0{+x%P~ES}zONrqX2*0iBat z<1TcaZ_J;soSIkXY1w<3XbaXCAjP>P{A0}4p|d;k49QFM?$HV*IdqgI+<9b?I^vnx zAe)W5f6?LhYT@}c($LW3%lsMF^Etli#`+Ss4fwwOpnQ(=;}-mhQ#jYF{(AU&d)$6` z{o{vz`G)cRIKQg5dfsE=>gfA5Li{ah*VZ!N{qZa~1jevuU#6Ad+e2r<7gn@pq28Xs+Db+y6^bw^jTBk zQ$FXZr*^Ml=UeNDnBu$%z96iUGMn3r=NCdH)+E6ThQmCljQq*VBzz+F33F~m^Cycq zX)`av{+22h#c@Z?I^OWK;U3iTDz{(o>SHi%x>`w4N1D{d?3mK*q}$b4>G$oZI5}rP zg06pBe$pSsIaIrXek9KaACH4|Cd?7ERJ($l$UNMw$OzHB)t0vI4RFb{$@w94 z88!vU^GWa9V>+2>G&%!VcY2xZm`9oHn*3~R%atopVyzhuA?7F!vrVxdGGG(2wDQ>0 zG$=H4B~%@VfKax+_z;oM$=gBOf-=T{mNCf_{LLO!ilm+3#USNT1Mx8J9BwXkQ&+N2 zjCPN~w~UGX#CG3Z$gMTY%h1;V9n4E73s2vqgh4;4PRV>^Q~Re$IY?Eq0#uiD z`{^9<9S<2n-Y;bxqgr!~AQ>MrT~SqsS2(!uG^WOW6@Ylg`^->^ihrRk9xg)=A~J zuzHuF)zVD4Ak`uUt;iy**Q-qBse|mfFtc7V)~UjssknehWhQ%m7O2x*j5}pf_a4l{ zJF5HlK|y;7t_#aw+VwjG;AdBQo+f^g`+@GbE=uCRub`CxB4iz7C+`6bxc&LL#)eb9 z_Emk5bn^ZysR~>TCU*0<*SzQpD=}(9AImo6c5L&azgfJP>&w>@m9D{Uj>1QZHLkWc z4KW>O^Zg3GD{pw0$oz5PIeFM=7G}`mMKr2G9N3wxqZ~u?HQfrGh(L zl%RhDR~m;k$M5Wc7CL)*Plf%<8%ak_P!P+R3 z6M6;=yxG4-dJ9iYv`z*5!bny-x%fLdBmdrT0K0rUp;z&X!&sPhh$1usIPOdz6vC(8 z{nTs3`^kdI57+n+GAA_lD$GkRJ5*hL&fb+teg|?0V(-AMB1Ztn4#p5E0Po$5&|=D? zk$&2d5zd9%5HjqE#xcg^YA2@#LBSP5(5oQoBH|^((5}^7(BN?qk1~$9ENgiN$|z63 zqJjf#taf48G#>&r0mc(YCl_mz9O^iy05ud@3Ww%j-$UqSg0K&NVI}0dQ|B*2*N9>r z@w{h5ujse@CwH}2MC67jfE>xlzG|-c&&aglRzB#`r_^aRh&R@Ym1xem|e7bV_|)o z@&bLjdY`q{V#(Z4R<~CRgC5O9uhoqy5I$j6YKnjEIkK{kmLOQTNLhC9fEKE&+;U%6 z{J98x+rLVfL;4b+p>S@%3^zAUVscxpAq2`tgjuNBJ1w8S1)HC#QFAyCtdu8cV6mj_tjRuUPZhXDR^^rTM24G!{*O2kc0Ro_q=aEH(Gy35J67*qg^Z) z$~Or%{#|f_B?7Wz!dUa(?};d|vBvxE5=omU=H{PsX(&Z^bD0v;QzHz<<@7#uv5iHAzW`+fXm%*|#CJ zr=|_SY}T@^qxOcteH?Zvi%4RF<{q0yFJNcw&sdW<#Z4j`gf;F0o1-1NO)M=OiY6@^ z%&=pMEvDL<;|;9%s@gE%K&J*B#K+2qkHD72&^*ZRxNaYZ*#*`x-gJBZu{wRbbI3`s z0Vh7xOohQ`uBu(S%S&sp=^MEnCr+ErWK>(zs-RhmG)$p~7He!AS)JgwI@KXe-l!L^ zja+EVw<^{!dOSCvU782G9RuHrE4G3q$entXJwOUV0q{r1>6iS7Z}U-6*x@W55MtGt z{k8;fA8A=1*rxY)OcIWe?SoMf&JpScd$_VvX08HFmh#(;hNBn|S$J(#U!uGB>E{;G zQ=}+}A}&UxT8Zhs)wgi6)3S&i3+ilYKj4Ds)#um2eaV?yY(_G#W=yInZY8o}mNp)E zF8-*9XhSkfD-ETcWfaYpVLu|hlM|_^^v(oY|30j?!%|l}#Y~+~mCPB@$Jbu}`Wv9r zr^+vBwz?V06>VZj8|n`{k(EGgTLWYHg_xPXm=%+r%$+6ZMlWe`mB(&Pt`|Han3Ewc zk9PM}r8G}jmz@&|vFh8&2X<<7BLCNHfQ%>X1CWq{R^MliRLCd=d;md-UGRP@q^^oB zb6s>CkxsNNxb|HT7~Lft?&cT8_9&r^ z>bjW1sOkaP#O4Ltid)gc_8RM6Z7EY|mFc19h@o?n^$82NxA*wA@xOlCnuD(Md`7%0 zXUw!5sY+3MlVYFwshTp~!{D4Q!%&z~(?i&TT5JmyF06It4a$LpqQR_WE&W&c3M>_* zoW2xvfKFP7HtQTr-vn^cr0G#4Rd5jDOK1qa_yoK~YxRmXJOCE+1w4P~cC^GG79v5f z#$C?7@6_u3O5)`zMm8l)pl|K$f-}>y*}MXmV-%eM)4&>w?Fi)dgGq2XL|i0x#jClR zRL2`Sq2)9cLT`lkwhhRDzDK<@? zj4c7vi((QoJ1QEYPt+U3mE_*e6D9R$;p29|a*P(A4WnPTU%HxCI_&7X1*?Xi9SDnb z_B8F_qO2$Wx#uCp%b=}mGK-)m^)Rge!CXcWvA5dIBjs%qxK^)@IkyEC>8Ohm(^1W+}bF zB;;;r_?&dO^i!~?C4Pz5X2wmwO$!p{>L{)SSXe-V0uYm?+IKd_5qoxpMnd94xaS!l zvtb?IuOs<#eO|pmMj-4P#_%!J6i}n<@s5!68Ab?lzDI)X`QvB5C@ItjqN+~exN$sB zFCgzsdM1?oDi|D)=CcLyJtwacpd9(9wjoH&!u)=fYS=5tS&)O1E)(hv?@OGCZ#hV^ zpWMM&-~2BCbU=&0fP~F;=%I$g?jxo6v1jW!L#W0er3O+4d>;JAmlMKgZ07z2ug2{$ z0CNSiUSj1NAR_OEf#HX$MP9Ez{N79%($l8Dw{j@_>j!I%{9)_YD8M3dNVgua?FWZy zfXk2On2N09a=mPnD+HI6qqC2c(XVw)`W5+S$5G10SbvlD^!%t8NWJ0u* zq>^HJ4xkVhqQhIpH64`pi8DdNC|sVZ8OnBM7S884Lh9c*a!w4?HPn*OJIO7oGh$^y zzzHjT#>|GSxiU&~Oqsv2EUH^hg_f6jC5u~Dl2&M5S-dp&h3k(mW$7s~A*~F9eieg+9Lh4x1FJX_Ehu4UU$8Oe!0s4QyMynZlE1NX{ zAOZ`p0{XDynBZCR8luNKw8&$&3>Tt&Ic!A;K0=hUxY$j}ypgv|kfLgp4j@xH(`&(g)!%9N5%I^f^eH%%2g0?&B zgb}=ogs|k5mst-2?uAdkiUaMiwyO?tF91u!vzp+R1b4ZRHFx>Hp3nB+uQ)^>*b;15 zdo9r|glbs(tT&L)nPqsaBxT!J1naC4jhSfAD(bM>Sx3+_l z4R3DD__W8=P%_-)N|rgr8vEjJ(9{vWvZ>Yz^hbOl*(;Pyv5r7{w^&}dqIZYOn6+O@ zCrSPj4rmIWn`V@!$cFQOB7}Owflw42yEk3S&PN?ewARp?F*U^1eL;TH6H%;~Drl`% z(nBJgDf`3BZo=&L-(iRPugAQ??)lA*M0B?IY@9>RTUM0_FOM4uxdDrm1L^Eg7Ga@m zfKkrIuR^B+_(KqoW72^TJLN&J`-~o)bmeM{?Q@U!znIY}j>*C#m>w>~1{d*FKq!G` zm(eG;S|oribiMeoy4%IF+C;@be7b(#u?5CK_I}#wJ)=+m{cx0UmG-)x`QWs?x%U#5 zXO;>BED^2(n{^%~ZH=fy4hUB?9-Y@(weuQeyr{FCC0K^<0FQ4cBNOc z3s@15e-Pi(irBck;TQVZ;+ehnPtISsC_O5v4>xU?`3{agx0e6cz)Ut)64NW*l+&6* zFxsYOY7&k-vrCU|l=c3|;`Fy%WL0V3+F@8QXIi%22akXZJC;2;x35nzyWBF%`$8JG zT&JJi?DA{uPhpocUW-IBZVxiE4*x@{@b~!N;WCe5p6;aMmRbQNv+|WS%t=zqx~*K+ zyhoSOD;Vem?1y=R3wl&IG_DHE!PEN*hov zGCqNwc%l53_Fwjh^uLbSkO?#p{hEwmEBSa zGN3K=J5G4qMSXEamms{W6$NHU@GO$B^V|{G!FFH)TKED(VX{<7Vl{nF7!yon*c0t# zf#|-@sw_39#qB*5;3(v}rkc;Wnc@UxsZj6{r>8nAn8-a`Y)gK@bjew;HaikwjxOOO z(V$jQH~l`6w^?c_a^w`;w>4Vg&GBruMOCkY?*^J@PR}qzx!78d-k4uD z5&=A+)5jhDQExr!tw+7J|NJBCt$}27;1s=7)HkvPlRfZ=205P~$I?-Zl-MGeofbra z-`&&AOki387fWXaf9JmXr-nx3_QS{OgS>;hcBkG>LXRFXruzXfEDc(pD(l-ee=nf$ zS)O5?gyb_LHw;yb{&Yzxi`HXdXZDJP*EHpo>h8Sv`)}FIF5~ywSR**WpOc|h^ zw(*>w{*#*lw7=CTGW!fOHu}#&`5W8M=#o~sYS*5^SHD<^tLJo#edR1RKhTkpvlmmt#;peRO-p&N&fp7vYF6}c2dHg=sxn-6?JDl;_aEDhpu z2Jvo(y9~!>w@o&^U3;Wq+985jEMF~m5BP~78qKG3QL{5tvn4Ocnl(#i#NlE!hR8bL##Fu#BZ@dlC$pI(_8y@R{b4h|Isoi&AelKrjhc}QqPSRAEbPHKCW4P;c4|grl!U*x{Ek)(^Mt7&4CQ=)`Ph=za?khuN-C5Y zyxH>PmTgWZJACBiQk>-W>cR_qJi|*q5EXR-e~SR~_n&>@;ut~jsWgg*g^*j%Qlwn* z_Ok_}v(5F>`yD|9ZOfc=i7qSszwzSymk1su9VFc<2Q_rd5Obr5G(~SdB zqrNaJsI(qZx*wk2r6CMyAOJFA4jj7!m@#Tx+s!D ziaXY52$)z3{$tcJk0bE#7GOlsVB>~NW4NL*vvo~34r`_rS$#WoY0u5E`09 z1=pKrkk-uStx-LnmF$jH&lF!yX;ZAYW`%AW_Keni3KJBd^v>30P6S2zX*fcguZ*yN; zoT@7a80>{s4Oq4P)8_7SNC>GZNm(hZ?<*}XV#9R9*&QRanRDGx zfOW)ug&Q!X*1GySVEl)G4AW~@l*;9$SfLGz=%=MFpG7kk5-}i&xICSy(Nr1Q`p-yR zSEz+sHhar9DzI#O*&~gt;DAJAv!v$Qf?f)$*-|#m$t zM*S1)Xbzzz4txeR#^Jd7R0t(wLP^^}I~6P1&_c6@E6s~Uh@V$&EOnr|1VFfElUK5N zOk+s*6aIo>zY%2HJV_xJC+jINvOY(AtSpznNP|Jj44#XJ^%PiGzW@%_SPZNXq#z|8 z<=|iSGY=CzCb6&fAzLN-jN@Kqp;{%r&`VOyM6^l{dKalFfo7HHaR*83k&vv$qF4b& zRR$S68iLgb7{lxg;HSy{ef-Sdr^xyplkqzumv@9QJTHfLWd81$+|?0d%}cNtnT`7- zW6g7cXJiLv1m|Zv@(y+4jlh6<3jC)h!G0Qx`;^`BlVCnQ0p8P7U_Fh;dFr7!C3?@s zc-lp9O354ve5ZZrO{t}bVLJ^XHziL(6xV40wJB%vDNLs=#HN&zk;Ze{L2F8h6A3J* z0SPH3Z$)sN22h$(d}RQ`DG#A3(fKZZQyw}~in{~=K4XxXQd@>SJ_Rb%Qy?-u0UFat zB&I;_PwJs*6s8Al+AspsF1}J0b4{Tyje&W{M_%%Y@uPI4$3tB@7;)*SXC3vdqn?#J z)=V6ur@=59fnPKlf;=C)C_lnE3%96`NR-iQ0<&lj8c{~4F}$L^NJJTZ4qz2MDZ1W2 zv|S++Y3PYihMovvXe7E&F2Be|7V2<=a`enRRG~g*P?ps83nL2UOF_9vLQjGsGzvk8 z%yZI8veAQjxIh^l=OG96F@Z9=eGqC;b}rLXpane$DQIj$=@^8dTy&sJbZmSwkbxZ9 zPj;VaRG>c6Pj<(Tfe17*Wok_7S~hOZ0ckmT>1Pi?{>dEu;fO!EXg`N0#APF!j7K#Y zjcD>&qnSJy$>b>zeLh<>pHUc_xhOtI8vk|C_+ugUj6&&g_&YheW)?zEfW4C=879$r zhH-Z?y5B+O`Fu}yZWYxmoSg$OcG3>E@%TFVlInO|ow1lYei`xwvIw3|Zi4h-T1EmP zCqTZ*5=c|%I6J5}S-n0C8E32#GXfDO9}Q>RG7@lnj77p34WBR;0VfyzriW;gJ>fy* zn_V=U>^>g{^=9Q5O7kFb>anHi!^2a@;meH1mgynKosRnekp z21m8Kwn118tZRCh5Z(io2=~$_h~)Min}d@KPr`hZeLD^z+jS$CMhk(3w`n~{S&4q# zZR+NTIuWgiY{Dody$X;6l1qwx&2j=*e0WNt(`aay3tq{Qgpz8B8~*V^_!V9V!CCD| zo|enz#u!>XuoWe1stwo~f$$!Oz@$Lh86yUEjoc0Agz1;=FJ{!8D3*EdQHs7?gmc{$ zwzQJMjI14 zm#qdB2vpVjE`@+Q#=}xkrLP+~XYYAltXOZVz1BW=!2I_;fKkkIiwQ%PgOBo0<-NFR_&UKX~Spi>G#!{Z zHbWyc;(blf&f=l)vZhVV`;DHEH7!k1s9OMaK>YC;hV1_)SNjwVml z&T{6~R^WilDI2ZGNsl3)SbRc8t*xQE9kMzezCqj_@#tHgvQHh@ExbIIj^fwP`%RsB zd<-4lfxq|8SxMtv&ocWSu>`qJv1wkG0Sc^H#-40XL(14wuc#VhyoL9NY?t`Qp`CaX zi*w_SVv?Y@xL4NI_~;9fKWbSyd%-SA z^~mUb_a-2$1fhX;o$)}_vOHB`zq`{P6x{nC#Ln=h?Y<5>r!_5Bn0RHs1ZnmAPrIfF z4o$VkB>_>d;dRBx@4Z8K!WIiw=s%Fw=a0ktLmT6rvcGr#^@n|1os#+St3wC6kJk*Z zI{C@X1Ez<)m<53`&&ca2mIhwom_kCUs@I9KKpMdgXW}Q&hLN}O#%xVnu?e{buU7X9 zoUym^^(R)e!|6oH6A_6!>!(e3ab`HbUYuD+qAr4L`1nb?o&)Q4%Qju5EF0a12r+|Q zdg5^bLKxcfpDy8RJ?8=kXQeIKeRZ%?7Yrau7#s}i&_zy#y!BT4o6HcmlylR@2JZ`I z3nJ5A|GlNvjJyisobji@er@BOszUd?Dht}6UD)FPD{1re4EGuAv7o}Uo{kcfbxYDl z^P;V2<8YTZ2k%2irZ>z8_k&me0JpZSw&j+ZHC$p|XH3W3Iw)rd!R2UHE*#(ShdKxp zk968g5CoW(@;u<={>0A6S6;4|5I!vRg-!rFMtf}R7IjQuAseW_aaKwaD+j>tc%jc{ zXxi(zf^pRpbkD2F-@Yxn8r9Q_ZT)xr=zf=!PKzarOW%^uPTs zN<=Z!Fbp}1ay~i?nr_8V%h)0XdzD6c=ANiZJc&WXVt^-~VwpYz;lB+1QPQv_oH0B- z2cEDD$!VV=p=B*Ax!l~;4Ws2tDU@!210=<$fp#&WYM)eL#VqWNDCiE-0c?KNqgv61gd$jZ`qn*5A0nHeK& zPx;vxPnCx;A_F78PVT|E7h@{kW#(HvMW#i5jzxaa)tu~#E^8vA$Bev+t$c}`b1;}u zacHJQc8T((xeep98d9;|qw^X3 zJsa)zF)|r4C@kZZ@p%i~_WUJ+)zKrGt6mc)yJ#yxb{96*0)x9cs@;Rw2}#GOH=|T< zn3JzFd3K_8&+_KFSwMBft+f~H< z?r8d@HrH zga0rr4sz{8Z-=B(8!b{BrLx0^_5je3q68}Bu-RnMS8^TIY1%Boxn@;M$AOoXoYTrd zTnux~_dDJ=BS z@O?yJXP@Z0uGj>=PJTQ6Q3^(is#Tgb!`tM@sPj-h$2$yG7=}^aW7GCVO_qeEa!~{5063naOkV6a4>|_WyrNej+~+a!syD`2Xhr$WLTy z7THcMwwli;eI{NQrPmyVIfEj*hQ{Frz)u1OW+5YHW;5k4|I@WK-EvK&usu9KgDISo zd$1I)=`C6#FCco;4EF4|)91sOpHlR@x#JS5N7$L3JwKoIrf#PAlwM!r>6=GsWt}> zF~&(18sj{&FsUMzlCiYO6&=2*}whrU|aUupWrN(f6|@!z;jCXa4E-72Ip5g z#^K9a4Ra%80#V~A3GB!yvh_GsH_r*VmZi7M5;V;YciAwi75VO@&o90^0S|>2wJm~n zm*@~fM|a1fW{ufI5Ilq}TzMc@?yb+(IzX-g;2bQ$xzg;cNZh%Egu9{&k|WSXB}Kqr zX5<11Ux8=oK2|*|vCo5h=6J#|&%Xy*(7i|265ta}Hs03uX)F6@#=$kg05AG@G46v6E=RsC)H!XA)#EoAGmgK>Vl%^$s? zr6)H5{AnzADSDajxwOBW1gj7mseS)nv+nrrE{|V$FYKk9c}e5{N9D1OO!rdfzn*oo zJqJ&>V-mw^f@dB^xC=a;GcTYhu3KOr6R0I=(et!t(F#}8wYy9wMsA?3Vh%iQ znCU6Au}(Sq?b&b1lzee+s9fMNf=vuX6DR_(h-!#fpT+Em>gpzL2@iclUL%*&1ja)z z&B?4qw^3*EM&tAE7<05YD}}C4SPYR+`sL>>ZD%iT1l@U)bLE(RKXz$)!097AL&%a|I{ciY91S`A0Xv6CU z?ih}yy?(Hb`7112M4&blFxxs$(~8}(${z$5W*QKA><}9YyEM~OTnZ$y*fCpFbUAod zk`@lLvhTtDKK2lVtzt(2J~7M(FbFA1?t95PMmzzzBjp(GE?aHCHh~n|rM(c%Pprfj zqc$>I%x~KsXk<1T#F1;pb5L=#L#U+Z5Hqa|#+7%SW8wCKR-u&YKr$nXh!?1< zi(rHkAEZLQ?Rp@L7Ap)H#t-~q_~4jrJ(;(~Ez{1w(QKf4Wvj`YRupQ)?8JXWAV1;b z*dvSzad!xHmNNKc5PJxP7X{R@86IXZ(ThsSiD5Uc<6UDMd;V1}*L(lV$eJanGzN@zNZ!U;}qV}8) zLoOJ;&ldn)ESasfh}J7x0&FX`&4@!iy>PJSGbjEM%_^Iq;MHhZBeltw;zMnzc`H<;5!GdEEHB)B} z-1|2jy^f~4tbNa02gX3(vBn6SiHPTq_?Ed*|J2Y06_i{ot*ksZvPNCW%|1CivSbc? zuuTVCe+*tF71N4rWJ?^JJPSg7dav$TC5h6ZY_*Z#ZZy?w(N;H1qr7ButZ#h5%HEgh zUML@0*G!aXkU|pgHF70mOO(6NT5T56B2k)e?Hg!Uv(*f^zv_{~Ie7u%!3YB{IwepF z!7vs`f%(`~Wgid~6E%EN0j}`5YpPdSqKbLZp?O)OIDVQybw;O(_6o?wod0W#^PUlM z)hZ2=gzb`?z8J(UNy=e3sYcdx3DQE=+_oEak>@Cdtvv<8qMEm2@hdJ&$B52`E`z1& zmEOLm8$uh|3LEthk=<=#&ehgH-v-EBv#qjWMj?&`3Ot`Z|Ki|fj#b~<%0cC7yV}-U ziwNsz{NPa-QQ;d=OH|kTZPqRFOtXeSYxv*=fH1 zROjWPKzg|0^>xmihXtE%Eh2ON%6j?~U-ODK)g~s?-QY|>G-Be8HxdMOaz`5!GJ~3W z|9tlG;@$UeFRosn58P?^FgM#SnK~SK@R{um8YbPXjdSBRI{>`N&BJiIb!#NhYD3s3 zu!-FrdQKCPnY)Gd{&k?R+M(hWf~aQ6JId8>@72u#w$;fyuFA|_={!T!dGCogsAi0X zQLS9FuU{wW_!>8^C}xhd;|u;jNOJs^$1#9x=}wch|RXBJ-<{SQXL@2*LE ztZTW~9+Q%5=)HPfQKfnDRV9mCGxtnu%)XYrnF@08?G1Uw)omL8)Rv3tK`l^+p7Ghr$YbEa7{_aJq9dT|hP&(!O%Cr55Ua0@SC!-1xpTXD>RK za)*H4@%BiccBPF@a!*ky0-iD>?|J=#ycVi$SeJa&eJ0+g9hqwDS~j{vSBG7*U@v0n zQjf`Nw38%q98TCL{kxM1Ir+3u=Kq9VsFNAFT>E-qTr61AAhpbyZ4m8~FG4}&Nr)o) z{rBIz`sM|Q$)!YSJa%X9KGrm{@hViXyXKmX_E^Yu-N-c$Kt$k>#L%v@vPZzOAJYoR z4vqRIj!c6edcsGt!qgrDy2_#&g( zU@-pqpgD+SMqZGt=nImeO^`jM*Rh4m?{8Y`XkN&sU=COgUWu-bVj7qU7d`96&&uFQ zmS#e{6m|J3f1O+lP*nl`k6gZT3_gIgE4jLjE|j};%}SY@`0Qd$f0Tl}{_4gK>--m5 zv?%9EW&2J2nnoq;Na_^*v2B=|GSUEGjv7p!AP1ix2NEM@{0xyGu6*^Ph6DGkh3g zoKZf`@J9-&ot*mhmWcFe#=EP+qr*=&D%y5 zVufbCc*QW5Xg0JXID^B zx3t`qV1eBdh&e?4@=GQbNe*60 z9dYE5J?198GE0a-<+OT1J}5NjgB{GdFs0OS?WLjMA@C)J7Hw7GTI+_fNoZ+`mivDi zDWWr+^ro7SDU#|M^!V7<0Qq@{a?EWLlO1-~x@*&4?VH~3DeVymQ%eXF2jHcJYz%ES zeOh9=fJJ!LD%Q;K)1S-YrY<@d>^l}MB(-m8-?7EMm9;;6&u^UJpae!+y~6N&u)JDv zVDC~Re%3F)wz)8Rv51lVs~cK6>?-Qf);)P=gkyI9>|QpvTr8*eT(73q2l~tmNPt1 zi=E}Td>_1T)w38a^}f+hsz-yU#=yja9XIl9%iX7#GU%h1bV`7#^Ts@-+U!ep?JKyrOlU zCCRmNgFikv#hoGWzMx{IB|C%(+KWo z@zkXtg#}oFOFe+^@E*EYN}hRSp$WRQ57dAfg$V(CxzaXY+)!`D3yo=k$U0>L zQ-QdsKWd9MSuqeMJRudoWn`iL)X28(fN?P#Hrl7fqQgOSw5XW=8RL+W_Q~E^g15IM zLYo}jVZPRsHzbm9PHZd(gnnQKN&jLkSV>-bHser&>PG$-kU-p#EUb0IDJ#kf-7e2P za1}Td0waW^@Re;7Tk+_!SNj8y_qj6E32(FJ5(5$-SKU0Ya zJ3>pFk6Nu@ow%0w4D57mZ{=?eyp{3Ub2r_sWPG0BV#a68^?bRA z;(`Y2+Gle1z=r~r<8_b}n9<{y8#x{3jL#$A%lI64JL8jkN8^)sQ{$8J@Zr-hglpY= zIDN}fkuxs*qGhZI)HJLECNd*emMP=mqH`syR%7Qm#BkN;>7CT)>KFjC_OAgK&*-*; zd_bn0V)=G@xs2}d%4yX5Gtsuk^eCk?QYMej*WB^wrjD$g_G)78nz;rp>4Ds@nDvVX zl9RbT_+Gq%?g3HH2|)<%6cZtHEPYQ>4x>;!&yt`xIeY07?py%TrO>Qd(1M+Z76w+Y z*qSvFj8hj3>2lsyRFoE3uwV@^681^6#_Yw*n@dtQ{Ejt>oP8BbQnyHJgP#8^^8|b; zKTsK7cdS_iN3I(-&Jw4XQc=ByWoGCN(lLP*Ze-h7pO-6l0rlz%J(Tj=Jyel~EsYA& z1h|uk=JMb$-NQnoJD1CFG;(xKDE^ooz2l{mZLl1NM-Hs1vera!l`AC+9tC1BPq1yM zVu5tjt1RfSnaje_vw^zVa%23QLiAZ^ItjzhXjh zkW0+%Vq_<7M0g3?kXoPZ10%cg^zKh@yYYWar+8x7>ymvCb=Za!w5rrL@`YpjaT(oo z2`a2BMg~~N(ya)DZlF}*OQMZ-qNo6oTng6#R0DV4>5u{9qivWy05P&VI$M7A&X&WY zsJrsAGypm2!52j5?=B*DVI3ZhHztqpLfg_mpFugf?Gb@oV^nuwV^h1pMR3eAEY$ws z?lQ(sC;)i?e~cisz<_1Z8bu8GvDvdOULjpcGJ|w85+q=phl;hOychShDKD-E^T+xk zS@(V@l)9gZ`JK)IO&N0-A`Y)z@7OQt1>0v})6%fA9Zy!2B=&l_V&*%d61Ug^PBpZY zMSDFu)s9ZJUx!oe5}j&cdF~#+aez~CE8SSX#jB3fp?JHU_7zCtDM?1!L)-0QAlo}+ z-FTS|GRgx2eI*o_J##+oQb-U37M+8E2eOt4vmQPm>_7n&EWR-Dg)xQ8h62g5rUi@Q zZgr2pSToGa`3pq=wbF^FId2ccfjaQyHHH8D+N|3Eme#(9D`JE>%@E9lHETuWV$zGt zJGpzF(B0iLs!*~em>A4m|Jen-^QLk`EM3LYj8ypU=^U_dtZ`+ZV7hq*Y69ZbB6}Az z6%l+6Q%CSQ#EqDcKSCKJ>v(tW!-==V_T7g)(IV#*riG7f0VAi^ED@wHW@L1m<30?W zgE`Kl4EJFmS!6k9bKHl4e38W_vLuk6l#$WxuiAYWT$+k&3T5fCpgtG^Vs7XZYi+sM{Q# zVc%Y}wQM#M-;HVB>({<9Tv4`rpuSAoG)(Af^Mo>&U%6B=d-wD=rE`~o`82?-_h4&T zVId@NWkOpf?#LZ&RC>l_yqHz;JFa9FxtJSq^Gyb^62#3P!Q}dFvIoDl1vlQp8DOg- zXHHcj>^~M{vpXAGGVk*^G>&^}sx@oG3-Z6tzWbM-rssb=JNxSw)BpI}znpzHga7!= z`5(`Ja{u_3^YgQ_zy9UwPw%f^|Bavj^jFcYZ}DG0o&A-){##}z=jVU?=d9uNBt9pxD(gHm($`FwIsis2r(Vx*`AzOKlqx_lr0cV(GUnBOJ>&Fb%bN;nSarRnsHXU zic+#6S0E-l`Sy`)5CHxodt+prs52^cOXlB=**D@=!k-(ai&W7P^h^sUvw&h*RR&xh z=}R#{Jg?nZb&fzoLgtGo{efAuQgMSft?P;x0DTXdDr~bSoA-d}`w5E2x5^N?6!x8H zulB_F0p2^*egH?Y)#&^BdrBs_@hjPxf*_;Rsh6q+Wy8(@<=<$JaslprFK#N2h`w_e z1+Vf?qXV$^o6rgyh_-Wl_Me_sv1Yn#c{%Rfe3nAafuc?NR&$|ld39&~>-~EzfP@aB z;RdHO-w^5oxIEi;9^$SlinZ*l2&wFX1%GQKtti;5apcY`$3N<0~ za~B&p+%Z(hY}P@M1~4Br%q%bf*ferYl%Ta*Newm&TGN8-NWsE}7PqD;IO|||6H=Vp zxaM%%`nf%N)yQ@^EVxnMx~QmftGl;fs~#&-ZUjx|>|#Kj;?lZ(U>t;!Ymd2%j;5Ct z_PbKM#{b0|8jB zr~nhC3<5eXt}j)n#@i~2_=V`?D=C&C&{lUs%{?HXoaY@cTUy}_fP)|G^5xAXcFUJ5 zJ-uh((#6ZX<27!Od^<9A_>?KQVO+&HG{Zv)Ou7NUJR1nvi!DiL)RrRmQ2A>99rcOljpHVVLy!k#0z-*xo!o~`SO>ii&%lWzladEj<6 zXPSy~N-INl*B@V|LInIt?Zsfd?`ojB!bV0y8+Aqr5&&DzSqU7~ju>nx_X2Atn}1w? zd^sU}#%Atc(Joy#?i$c?iNWP1rVtpKqkWona{d1Bdoyv<@(1wXP*ZTk#T)3XHj7|K-9aY)Mzk z1q-T~NJc8VG*}@uc8vvOVNK4!Oz29nYAJ#5056R8c_9e<#1%lG;M&60P>eG70*d*h zp#+C`6DL$@N$591>fdJv*~cX_@cYpyiCWixVP1dcFf z0e4am0Ow~VX@%zTup`sbe;<}16jCl1M74#v6zhgfoj8;t{(TJ9q6O8`hOe71ttf_c z)n#(>SmIgOBe@;K4Re>G=tLpd*aVWzTabXNjSUw%Y@HKFt@?`m=H)@H~{X93tfen4M~a>6-(CTaIa|z zH1Zd(0i?_rZa3({NZ4G%w#qEc5uD6_4Vfh? zN!fxl-Z9gZGcemEYoxnTEBe06_%FKfT3CyHvS!+AX*y&=^M*M<11uR13*8DN?EaFc zEz*W(FPT{#hPs3vV(0%_DZdlV!vX5o2aC{m(1;(n*12w92SDRl4YJF zZ>wfo*SO2Nz^onxb}f7%%ipkuUi5YuTe6~ehntu?>2>N$%)pSQ@VV*p9A8LAD4}~s zy4Z3ORo!EF)J?WOXbbYkxBdUv``hj|ZfkEG?(2LCEcdJ>sTC0 z(XpK5zE;vJ2buv%R6~FnU?j6A=ez$G_69H*avlJZlI@1`BC&=ud+@lipMRFs+kg!p z5)^?;wse;(X%=)Ti-MI1$Bx>6+%jmCF9E0(^m!e7^N&|mL@P4-`sL&^s<4ct3rGO7 zmkUR9-;DDZ*6l(JWT0^-mN=N_!J6TG>1idNDfgUVA-`?iA7dNIdp!9}K ztfeNr)ubOqwLW_r^zra*5Fh&p)+oYOvJVPH+B~*85H_?^#l^5C|6c^a?jRySjvzonAPFKip{_?HzX44KwVZE|mlW9eyJ@HV?1n9{vUZJFLge zqp+9Ed?}hF^mGjPfK)7Pbe^8^G9N8ysh4P@IrgoOCLHEVM!u9B3^Ifv6kd(_hM{w= zGV5RUK?yK>(fzDv`Js9llbq?UMlVCm4QFxrKzkV^cu!g5@WH{{`LMB(Q61Yw zPLn3D+(vFPtX4H6pkjMK8rbME%zpE-F4!N;*NzGAw$O$bB|?gk&C>VI8!LCa&|$PG zP!T8DS)mPD8B2( zh746@8H?OX)@&F;AlFBL9iBPW*^DWoWYlsegbSrB(Tga@f4jo$XQU7Z5hEA zGA@+yhGw_-w9FM$HlUg#RNFC1ZJ~fA4i4hBXg$qbtjd{Trw{Aj;LhYR|hg!T{tzt7{twz zA1ADVXyq~6;I|k%0-p4eMQ+ZyQR*n*-%eaH2=6it8oV}s~1Xtanm)i+(;d-r~{U+5iiYDWkan1U!Em+jMs%Me~uCRj-|I6iIZPCg-Yt*MQz zu<<~I%K)9sZb}n5bujIg0+Z- z)a`==Wd?Cwh&3>{ZtgYly(Zq)y}c&h zcKsGK@qmYI+*L1~HI0iwr#Iffz&r2hax9p(g{YiQ;Ent4 z*CidOJ|13HEekfD;|i`+)&{NC5|7k6UIX4gP&Puq{0^zcNleU-?3i3L2bL3dx)}r( zP1|tnoN3AnRiCAA+t2T4q6ueV%CH9K;cwo$gqE-^^mSER2f2V$~;~h#*kvFE7dHrl0iVj4qe=S?jJ(pgw{qr zyBekFFyu?)kHY00R&e)N_j~v&Fp;FQ3H^D$MuIdyLA3+7VH-b>Z zUK7le#9Dzo6NWwDqUz4k{P)JT{j4ymU>fre7;)WJAJN6<^Gcgd*BdMqUJ2n2WOat$ zgmVy-*CbNJpArOvK+VLuAkhZhNNtvEu(1#&U_pEeNkm)6rWOxbHbLy`#K1%a+m0KdD#ipTj24X`I5034O6mOD{v(csstj0@<*_ z0@Je3L20#G4>@^MZudl*23trQ!%49UJ;ytJE+nuD7#|C%Vg=QQMvbI zP6eH^ktaK<6HI#%{KG4m0l0E5`~C6T-5+mUsi2h|f=XWekqcyxNPXJ#$rpT~&9XmX#gwmDF)uNOK#aR-eN#(l8(ibcVsa;t$~4=KytY5d-K{%C5^&346iP{ zL!27`EQ>>v3opdsuu}qMM$E9uN6^oRpR+B7!B2amV8kt34wU_0zp(V76l%s74&xp6 zxm^w9ds^_6RTTCR4WpOhn7o$S{O?)~I*MJ@%Ar5)W3wt#IUHqW`D>&U58^T~Sq?1am*F@rm6s*jf%NDE!R zkPi6TAllHU5CY;W)odCOl-s=Kteip#(JTrhvAz<|D7-dY*P9q)HZv*>p(Hk+c8lgi zGDCqN^b(R{A4hfFU{2R1H*7FU9do)cT<3p`Z~+_We-ji{UaB!UBZ`Y@!9p6&1r&TP znyhJ&QG|Y}_#G`6=Dt(mfkEYDP5zAM7Kj9HL;`OHesar}PmWjlQZ&UUm*PoXB)Y+a zybK5NZ%^R2$Qt*=X7#R$z-glGL+VZ2rYl&V-t&T@>pk5syh@=3O)+Ymt^+!CV1=!+ zu;#FgBDtkg6UsBiCF~|K1f)L%9^|eT$SNq@?n-<$sz^qZNO2eM9Odu9B`qi9MI1t6 zK#|Xlgl>z338@JeSyAQaGX?VKIenNR-Dm;Vuez1%GCI67G1hv}RyzPv2YhsE7FS6i zsrQlmB1SYA!R+)r5b>?SM<$(R8k$(6SyO3x!EZ`Np3kTdtO#lhqs6YX%~Wfe%`n`9 z62l1!rVkXEN${3tGcJ7fN8u|)4`c`|WGC~FQMMCNR}U=gs$HHnZYgg28jbbUP$3gQ z3R``@29&dG0EPw+VSomOXZsgzHx4YhoO@F$7ZX;NIJ`H^zR*jTPe>(#jAq1voXLBV zOLEU3)W%375Ie78z+i&jL)uv;=ZjL#xr+mLYhvr#vPIR4_S2l$e&xxkx zlxahUh-!Yrddqh17CtK(mGZ#DQZ{?}r)xIvWm)9+L$L`Mq;zNq9rg7jLLp58Iqg@u?(cb? zFc|Q7a}ZqasTl-fMuG6032ioa*)QuK0<|M@!LjHZ&<5m@`3)C!1;cs*hHO`6Upe!H zVj9b0L0JQ-XGHONRcI=htW>e=T~3cV3B!kL!QSCUh&yEc-9DBzyTTWzAR8!tr{OM7l-fPtANHbY8u_YCJpEP7{9dX1aZa2tEm-&otz-a`0$aQ%-PT>qhw%_mTjHVzTTKD%Pk zus5xc6<8{|tr;B_47+FTY3ho#xewU7ay{IwyyGrak(8AX-IEq2no&&^(?jOCrhHbv zW4K4SN9<1s&z}=Iz%p!gRWkak74t-c%0!laU>uT8KmT<>LGW!e#wE)J>ib5me%qq& zzPmh69VYXR69nPk&7q3$v;v|C|D_65Feqa02glOdu9t(mXqvIwWO~lkZ5+bX+H1|N z_4V@$V(&(3F70;0Awyv|Sy|Ua-xBR;nz7nBk#lvs4QkcqrpHx%!C@=Z;3vK7Z7C~q zZ%b%fJ>@N5JR=uERVAzOZdN`sw1y+K7IJ~r0D(Y$ztq(;r=Ro^PBdYznkjo}?|L=# zek{sfx@r?0(lQ>#Ub>*A26Zcbar##rD-3RkZCB^z#KjpO+XBNL$jLp_uVniwY339P zSKteeJVH)#c6Xw3`t%r@5R;GE>?c*^$&|J2!tX@+8sa0G|(z)X=o;uC z86>eHN#VmQ_Ft~e4XHSwbW%+yGLx(-XrJE*t-oV(esT5Y;`!Ozi}PpXYx@Nr3eRKm zR@Ua?9)-szDp<%2-?n39!i0(%A`oipQ$a2T*B-#RU-ruiDQqaVp=zzwQ?8Fu(!-CK zpBrE4Aqu&!Ms%#xNw*Q%Ji2GRlHlQ0UFjWkKGpZ}NE$R03+e4NM3x8#XHnG0eCu*<`Xx3#2@`c1bxvz^#+EM(@fNUKRX1T{ zE!@$R-`Q$~Tic@R=0s0%usNL?_~zyI?ZDlb&B-Qz17s=sYB<@!SxK&=6Ol>~}xL8O$ccw~>X6W^$lHy3B; zuP(;(d>a|kjJxhX+(P#uFrNe(3nK>_x$PdhZ zUjo){$umvvXVL?KLf?jG6t*fNn%TEc39;_=v^!_E1J&~fNJ-b%jSfI~ZLyO#=s z6*-Op19E(x61AKcT-??$Jrilm05W2JaO<~Zv_G47^>o;31#Hdzb&{1C49aGo{oSz% z8|)D(^G}{Y?*=6H_uTUc)qV?}%T=a$9K(f28{Afq%j@UYmrcLOhVj!80o1(;a+hDE z>oA4h>_%42W^{2@>Df6~P-ZY*Kv{b;$iYA?T+zfQ@Lak{$|NGe$p_Lgu%?B&7 z#f7W5mR@6KR&o#Rdt3C+QFw^n?KXVA;l~=Vyi>h_AFH3HZCSD(tKWb12%-2}o;0wd z|6P1nTyV{)mSvLPpz)SBp;SuCIqonpWAedl+t*;syPT{OtZSHq4g_O&fE?;<(d*ya zc@9ibiBMd@cD}{wcO-p^0i{ww&6yyOZ(bA}Mt;bE>U@F_@c zS4QdNi=Szpm(TDFo>)n$?3Le$$J&7~O6SEs)XRlM^LhLH3aBXZ=Pyn{lKbq_FHURsGgoJo%@Zp09qK7o{Vjp4P1m)cDZi*JA-;IxTYr=3`F*9r3a z#dX6|x8{-SwxFzf>b6y##H4cs9?0Gql*Mc)sb)|B{;V87q;0OJ!{j}=qfF0nG=@A! z>5iyj!D8#+x~LHy7F^1d49o+3-pMRx{~+@z4f{8YpD7hnF=lnuz1NksmBDvp`>`pT zU$g-x#&NjTSMi_M4(Fuie?8mEX0V}sxE;aK#nI4tfEU%hzIO*pQs2Tmh$OO=_1I8I ziw^a#tR^3K4A+oCvObiADtGGI@U<{Y`5`#-)?-Z`jBk6k{lj3EXz!r3>V3P>`G$Fh zUo#tXhMCk-Y>-*g_F~g%e+5rG$DFdF&w}EKVEnRoB>|GIJrkvHPNk+xX^TDjTS}|n zrAdF=t-8n)64}hdZp<&wt%T_^(Ue_eBx043^_BMECwhK)zVXJCrM{PCN|BD{D=QH+ z_P5m}(r#_!!`ge-u=iFF<+}D@zeMbn*2FE??+5XIx0NmUVQsz5PsxF*4F1^c?afA2 z8Mf9xaLFg|9QT4XWSEJoISvJ*Rj`*Hnv1yBY|KeYX@2YeM3afQr_p$i*cnA(W<`n+ zox>JZ=nl6>V2L?hAkaC<<-NG4W!?ja9F^vP3vs9{W;4;De#fHl8)Z9Z^!u!{*IGffKz-iX-!y>w4cPto>x`sJ5j62L9VS;pt9L9THI>;777$ip#VTwOOh`yr9h3ia{q5@-4AB1COW4-WHEYBV=a`%a;dh~} zkLuYVE`*Sp(8&Z>phG;goWT_w;nSIe*fwjd-~;t=liGv2R95x%a|a zJA~fIO1lUK{C$ut)_;IfJUBvwZ=~UPBowP}h8w`~(3DS(38UGJ+_L2eSVI(5EKTk# z7U!n34G`J@_4|3ks1X)b) zrRY|IpX}q` zq@gaHA=^*8n^KTHHiGDwN7ip|ZT11k5skudUb0A_NxEcQbW+-?EomSp{X(u6WRqw{ z3B5@ay?{I*1Sb*g6_^8|IEiQ=KwnOPnY1Pc%H0UeGZIHiOj$9Lf4LP42cm65|hH`)c|Dk&48Wg2UA7Q(nE2@t4xj zm*SD@(*pe>d-urUfOw?_1xAvM2_3{MaXmVLc!POzue#DyJ{71T$wzijmc;OAP=1`a zZNYF8x;qe4go(N@QZE1Lj1N9<_@QfCl5QwAQwB-wE@TwM~J52;v^jX}0F z+$gDR#^y9x&N9~vGc#9Kyu2^D)(k5svU0A7oTS2ll7?6D$=%ZgxIMf!2F;wfu52~5 zS3#0L+AKlEE>e{r5=3ly3z}i}cQ|*CG%V?;phm{@LzyfnCxLyEtuYMUwVg$##S-JJ zD@n7|XNfs>Vo$1a)n|!u)|E7xJ8(a)(Ts9OCXpPn#K2r7Q5?3!gw~ZL(mj@#XrJaf z%_tvdvPG)j5<{{G`8vFQTb7tWym6;y!zz<*=m@iLoN1=ckB*2vT)nI_#~Wp2*(ic| za4;Spq|A=5FJslVN>4_ACQM{9$Hi{iT_!ft2uDg8MC@U}5Q9T8pb6a4^uv4ZI{S1dlV4N+gV@qs2XLDIDk3B*2 zW8%S9x5iRC0(m^E%906PEFW1i_v06fBpY7M3H8-y(L&~Buw?mh;;V(LWgmtc!Vyhf((PR{dq(fzM5ZWdCr}an2GJNd++n_a#s390)--Wb>(gnBm!5)H7 zv3Cnz!eYN>nH2dZhO0Fiwa5hSuxJxYULhIC?xh9KxnBB+FbnHWaet*v%$cg^oY2Lh zlr)<$6|{U9>94+QnGv6WTvSsoP$>Y)MoKJ}q?DTK=oPfy#c)-*PnwTJQbdtWmrE$2 zC2ezpap1v5h&L_CB6sDN|9oon-^}iIbH+eHNG(YbA2(o5r%BkV#wR}goXliZs_}W6 zPf1Ji6y8LWlGCp>DHv6nJUu0IE-E9sL=g;BmM=a}*oN`SO1zrVsh}(nk;-Z*$aP{3 zaj@PHv2G=B!yaAfOGRk;030h`#*dWPqXm}82Zu%ZK|;S&@@A>y$|>1rn{|}_%idfo z@!Hw$buh-d!fVG%F|x|ElvQt0f<|B9X{RAOk3yC)1!w4a4OuSweUw0FaEczR2vJ-8 zAU7lL5$A(77#6?h&wAzCwE-QWj0q zSVYIwHAAs#+mWhNLQ!Ay@DTq|X!U#J02 z3&U3W^)XjHYGt9^EO!AF9RqKSTc`7ZUUoWo&XzZE)y59Wo4m=x-q2hs@>Lij!3WH$=L>Jz53lZ-89Czkr= z%xK5MGnayEStcN|yEpI@%w$p6YHo5up0mZwlT1b?*m3_K7kz3WWjsL6fqA;Qt>6ys zs$k)iH@!t)A7dEb2W-Z!!M7f5n0621bt zdCnB^{Hb1qy(9%M9%FR(NMav7l30?ycmN7xgF(WbH3muZIUmT6{vcRn1P+*yt2%#m zhw7#pnaXIvF29Y^%IvdE#q8SN3~J~Ps3BcA4&V62Lxjw`9R+&~ho(jpta>ahzwMzt zrJCK-_%qu+A0jiY;4!&X(%Z$et4s1VlG+|iqLdoN&vQAaJRU}N^~F`to!HJ?c{;=r z3P6e{d|Da3)#{V)v3&oUNc8cP@81UZG_cFL6oVT5G-i6?(D9KBL@}W_I01uL77(Q% zKH297pjg#!nSCjzTsZC^t(SX3d2c=1y&Uw>%Rx#N7lPgt{hrq5g7&YF7X_UX27jF7 zT$%rq>+6@`NvXszB(F_;xBS;c`n927}e9Prt!n zqZ>xv&Ukiv6*y01Y3_;ORnaJ)uYP46&(C?l68uCi z*UV0~6FV*7YCYYmm9pT&Q8aBq_p96Nva2X^-pecp*-<)=3xw$0qK;#kLf*1ZJ0 zWg!<;(HDfa2CcY-{{g?hjT>qc)SQDbF{eGdE2{EUOEy;wc1|zNm_k=w30l2`R|;G0 zqF7Z_aVm$M6B&4N`o-s;49i45RP>HfP$GRhLI)dy@ z4`aL@;7G&(%9=_Vci zAjO!TWXL$E-)S|^xVlk;;)RXJtV^nEX?WlRYsVyRoetX&@T;;@)M&HQeS5v)MD@2f z#NpoiPJ6D81podw3BtXN7f&^6YdT{bFb@#Z#O_r*TEX%M zDQyT}A5$ca)~E6geI-C|gYiM>U}e{5auGE;_F%Pk1V2`8g9qYAXxtg%94KgCLH1@% zp;@g86*XEE@?H%#Gd#%6yqo^nbn+WU=DaLrX}vjNH+pG8FnYK#= zmYX$Ez2i!+k@M5#%H=<>#CWF`=B)j`H^r>E1;RI)lH=Vq{IsduXsaAx2B%9V zc^|T4zuY)vq5VzEjo$ZP@N9W`o*pxR@43@Q`FH_ypL|8t02_Qo1)ZXi;qdzEt5-*6 zrGWM5@_f|;9ZFmHf!EuoHLeUn(&@YQ<^v4F4l+00`85Ph)pBxPtro1n-~d+}M>!S} zb9&y*^V{t@G8`08w|X-M+xLYL%tgx&y)?UQLai^Y-Gov?c~EfPt}J`!cl+xjy9#;B zPqG;;wK|!WbV3Ck(J2#pgvX04PqH#sCpI*ni|N=tHqPYy=Rg8e<0YLJZQnROJw5&6 z^UvY`r>CdQ|9|%BpPqj9+3%iy{-@8rJpJ9C`YQ(OjRA=atfO{>GvqoU;iR9AYu&Ow*id zYWP$DOo?nOlR}rWC|Eg~GBGw{+D*laoRvV_efsWn{Ga1bKe6FpT#C1R&XlI}#WNx* zC|rf`F^xsnzA!&A5{dabmXIMGPa#FuOsd%w;!N=G5ovA&xQXL%S+@Z07F_9XR)2ZP zt&MO|R3$B%?MCLw#k4AD+4$)bf_mVyS}`zZwH+pC+sF>Y-BVgDX7ni%1I$ZzP{;)n zXIGcse|Futy>h5N37SjFN(2Q~zQ$o9XH$#-2sEwB)qdE+!W_j@Cd_CtqQO_A%erc0 z5VZ(3Fu?Ohb?~H;b9Qfz@6fTSs%!?a8kLqRE9nC5VeB1<#O3J52s6z!L4FdAMv@tA z6cnqgau_+{rJSlup)|FnEpqaT>5^x`O@~`)L@GNlFfN~jC$&XDQA{6)u_{(%|BKfF z|9S%d{;RWLxzBhKp?LL^ATOY|kjr&!i=CCWJcrdtPZp)jD~xPiY5OtMM`%LW`H3*I z?Ny6l1P>aceFJqWjKKTMG^hS06iP5!8^r)J$2R=TZ6ltwHS{Mz-nyQ6jH`g6kmR0X z*_(QWxR|<|C82_RYj@^o4&rK~4-PdBQ@0!9?xd7+A}g(o&Tl6nd=raDj%&TAU0Ljc zu5DT?%_g+qwCLZm(L#8S+Vl#q5Vr-n%LxQ4fIK!P*HG6{xoFKyiaS*Wb>etuJFS*nwq z-Lc|C@#%<`*^FzJ>8fNWbiqf^bYU5K{(BDwbT6 z&QP$rEpkLXycFa)owMRO?eHeUX)$Kx*4cf&uq9HV8fJmY*dL`A@9Wqog=NlW!R`!bkSm&YfjafAnctkYb1S$ zCV;*7+!JY>-Tn-28CqL+EtH6Gw4FuOSsHy*W z_MjmHYfrFZoPtK+H3kejUT3mai^Ze!SNZ21Zv^Z&9EQ89$IR~clNPA z=x509-ftgkkF**vxuy9^yF;wnms?{H!*#YIx|Wq7hip6@ACo6S=Im#u?>uQc7M{$P z>X#y~A39p!B|Y`NUOt#9)sgHW8Xagg$W;%L01;V8tm@_!Vb8hWH|O3I>fmw3X7Vw8?Z_`6&mrsl!s(9GcK-Xh@H&H#u{5 zBn;VaoPx(S>P81$uKgOuC_*g>y^}n5A2TzMSBnC2@!hA|S=7)n#~7rz$*_A4kYeib zs~rh3Z}*$qlEfn1a3l3bKBV@e%+gQ?O~t6C-)lgpk$XIvn+Zcs+TT9TZr%y7lJ z=C%#HikRR!>fZ3?@WhAMoI^fl;=wpk!7WzKad9(@3*YDhTVsqn%yEGh z^;aFmsv#xOaS$%DqOze>UQ9X(W9h>c@cd1CY!+_Rh13|iS}ehi%X8E9ARoxv2{#tG z!coH?ZlJ6(2#4ltAai{mhTFBSEb4Mq+UunbiDvM*zHo>}qpyM-Ld(F*;1c zgo){tRBB1s$#h}n&B3$I44FZx1UpsA1)aidNA+#>C6Y7E$~oB0%_%#_ZDdyR!amg# z%Hr*?SUFpULuC`ca3$T-C84FPL_UTSvy&o3PcwatyCG=kYbn5$5rBGPf(%#WhV?*- zb3vYtpPoLvaEvK-6@Ol>VZAN=55t#9LG4%Q&2 zY%Yaj8brEtDGoHG7}?f<{9i~4l9scW$ujU%w9xjaY}4%5V&wb(!MAFQgX%yL4;&+C zG9g?j&1k-*pw|pHM~7aGUISEdD#mVrp&GL>a(81#?~Nbl7}L6zni z0E1j>SwW|nf{Lvb z*Nn}!sj&23G3+ixrkI~QAtUzaCBG5$lD0+B`~8my&SXJL&HFScvJSoAUa5oS{f&m8 zHdsodWr1E!h@5k+UHOl^E+6S0>Lq!zbYI6x=YA1FsGI%iBxiRgDyL5|g3Xp07}!u@ zKdB;5o<13qYbP}}YL)osdMTRI&@}`~QlCB{hq81rb);bQj=9>BhS4kun=kb8=n-pl zZGlRGflv^#w~{Me#w1yH2AC!pCx#~+K31UMz zp2UY|bNVkS$i-JKz4hol4cbo9TQ1LgwUM6eLHe(%WJWHD5_J62tHO=Bj|Jo4SBIt6 zBjdQj?jk2Q++`7lLdq8MLvc_5~s%1t3}T$a%;dhy+x%cM?x_vX^|!TeKB z)|wAZ>}Qm_Y5vCfs4w>Us4Z60Qd>5ImW*7_s9~gDXCGej zW`QYl%J}3>hwDzx85R)FpE;-5;0lE0+DZ&FJ#))liHoE+i+q^kAsTOme~sQzUeFs} za1C`e68E&yBE2{lRf5D}g7%kWw3IBxF)-@Uoq>Z~@LB>t>6Pq&`c@WVACF%KvV z0sQ#G9M`|$N=0eObhtH#_1Ch%Rp5&GpIKf6w9OoU(_0}gE{Wc7nza78 zH(d9SA;N9av#}zZG`xZ90q%apwQQNQzPN6n71yZ5{gdI) zifgdqEjI0Th@NlUT##2CIG??%cNGIP1$+%B;3Gi#OsUOq;7WC9qbI z*7l(W^XLb#aQEd2Kl<{7n_;cW@QCU{U+q7o@)cy(4~82I%b43DtY%!~-J`x@(u8N5 z0;IjSQsAv43IMO60&h{s%#a^7!mp1O0sifW<2e2>iVHhWT#{*SsIf>a#^t;9ypczc z{ys>(KkD4vVmKcf^u6ZGP%uGZ+)0EeW~(w-yMr0E1uf~EX;ylu5c8`5EC@-yT`+O2 zX?EM|71_%NAH96Ag~Qh^8(Ua#8Ac9pVNbF5xf|cJ-`1BOh_fX5yzr(h;BRk=Ik?5y zy$h^BxyK6JV+HQ90`0X4Rv>=_tiZY|#U3v3gMkb5lM;^@*h>@91UEtq9Bk_zF>sF< zxJL}!BL?mf1NVr5d&IzDlgEc^?%27hBWk8MM_6pbR$MKz*4yC+fLDxINa^O9Oz}8N zg5u=xxlg!r0YsNVvvNW+b`)9|*kZ=!tfWP^&C;~9lMUsDKXUpbM)HMVm4GO|EN>lgt-yKBmz}h?`Fd)!Qt>LhU#1I%BMb0+0vrW=}O6Xt2zx zxkEL8IjiFpEULhEOE)4x0@}H$SewgxaZk(q?5Z<=tSOS!&xb;(yA9a9SUl3^F!VUQ z*1coz(+k#SV6#BQtDdahCDH2T>NDRFHE@b;;8a6PSufSSQ*G~5`#79xm*`Y8C#kEK z)ZjO+I9I=wF4b>wUUNDWZ&%a40!e&Gl96`P*0UJM_Lfniw6r4+2+$f(U|RZoP^Xa8 z&_xFSZF(+rE5uyU@5oE$ge@}As19njs~mEe{lZov#8m)6&M zfk1C6H;bjNsveOFUthHY7Pd8x>;rT+&Ol8-yqZMMVx%I1*JkPnUYocP2jq`X#)#V9 zwfpecTVk8;!-i-PvkQlXk5vI9hH8`u(iAfy$i}!21Lt6j^C-f77)TaT&e<6EVIW^b zv56=Nq#LIKc0^M^B;da{AmpT_`}iPj(&B2{PF1M@bJfPUVZ)c>f#^#=+_@bHNVBb zem(q=UHl`G$kEZ?{v)b+4T+BnJ(A@JhrSgruie86C0( zN?&AJ$efw+c7(7^4f8L$P$Ty0p(S!;{Mi_N zBCaI-xn{aZ6)i!}wB=+bq>vdcBFg2FrW6B<=fdq(=Lj@Q$b1o{KQN0Hs^8#E7k~$K zQy|7XN`rpcyoH*+@A$l$lS-LIE`@!jm#W<{zJd3yygL*};Q|OY_4gr};M$L5Z3=>n zQm0<33X~1o1C)PzDar}B_r18Od`0xN%_w*gzZ+cvYd;B5`Hb&ERIp7_U`=UPw_I*5kr?9R=SP#3`E*}n4-zx7rcd){Ts%$987 zQDx$>84fa^YcewbaaJDZ+Ph&Bj8y5~E`}B;);13&X9WYmh#B+s`Y~)~F7M2${7I1S zR4V{{eeuaNLXR40p$hd@!B~!oF$MGF%SxzYa&zQj1G@>!2@ys!C_Ewqn2$|#Kj;?lZ(pdEygD_?UN9Zk;*%ID-zF-9)jlEz=I`{iUbLzc}T zG3n8?1Qr1cxbcv)-B#W=BhcE&BfqR#f9_rmB?MqOPyr@#83c5kU0teBjmJG>D^vG& zzK~)X7TT(Js5u7&;le23c|{960kH9%B%fbjB3VA2>Crs{mo6UWHLr1vTgYwG=92x>g-yVed4p|6H4}r8$}SC7NKHZrQ8Olo zU?#+>2ggK9348~5VJ}TUeX>ADIJPh~6r;?&fMPyrR)VW|)4x!qT|$2lQvbm(eHT3l z5{E;GMVK$c0t8s<2Zq4T^39UWxzhBOjmfnUIKr3(oJm2TI6uotB{c63J2G$j@53;J zLdxaD;=GbY$wp2b8e05q4AoW(T1XqdZoV|57}B-K2RjdQLxtUvt3lk*cfh4`f55~J zRI+&u5>TgakI8Ep>0-5TV@6b_MIrC8IGO(%GE0_|yaH*wW~M{Vz-W`Kk?xX4^lhE- zpVi^DFcxjH=CIe&5M)C0lDUEgW`Wy*t^_XZ=8z9tq!H#@t2!zZ&ojdqt@L6|HgMR% zGz?Ou)=JT?7~mu-Y0o8d`VMtI#W#mQaId;?*qhf1W?TJooppg(Jqqkv_&}7uVGdOE zD&#Mj(Ywt}%(e77@(?r7q!D~>^fbm75)n%1u0$7GOrom043D~w_6Mz${PFKvR&N6~ zd`M6PE|t3&H!SEG`9nj%vBI#|=rI%L(5d)iJ0Y%URP*r|Pc>QjAV_BITB^+$it{2m z$=^ugCD_tkuB2Jer7Q|o!f;vbKW-T`%9jAt3i`Z`z4^zhDuUfd(RA_|Rai#S1tfsk z%Y`GlZ^n5H>vkbtG|)H`OB~GeV9}t#CyMISbjzqbg5DJ7Ko1t5Z*TM5iyKoJP0GRYSNFQTA#fQ`gr&@h>v{)YZPHC*$0IpZ5~@42pig|;$j!{TC2hmIITU22<1o( z=711d2n36JdW9REU0uSAU00|PO^(BRZXdDX<((bJV zbWu=iqMB49!x%@dmnqSCYl;F`H56Lu_yj!(wRItmrMiU#mboRKqB93M3yQKWBltqb zg)-jI?Dn3Pxq`|DRC9!CJ4UH36tKj>LEILtrW`i7N)-U8X^U*CsMfO85i$0et8IB{Z)Vg}hfp z-%D@xLg_DVx+azzspA!Oz|uA1rJ2fX2teFZ1aik_uivwxF#qr4*aN2Tp5F7Rz_fm@ zoN3Z#)g0!fTr6S*C9~F#R(lU&AmtrX5C!D2efn@2!fVO|E2##@r!C#dCxoswwXqd8 z9*A%mpp)56X(Fc%rrlBibD~)RZu3S?7gU5gBHvl;cYnV4gzOIcK%5YSHUzW}py$S# z2*%{_tXRx~FGn6Mo;UfN)#j*#?T(`Oyp&44c4WKEAg&9s1_sy7y(Yfb#M`>J*Tmbd z--0F{@UV@$>ZP-$aWUxh#v7Qtmf8j?fMiWcnhlh!X=xOA=RIAH1=F?=mGcR_ao_#A zqyyE*!^^5=!Nzl3!IjF|pw(L9ky^)V!21WvMhKYSA=NmEiTRNolWXR{a>7nGgTSI` z8;+ebO?jc}v-EBI`5jF(;VeuU*5Ew+&0Dv823)#js!l8of}g`keE&eS!>Y~gdO0C) zSSIgSi3{N3-GU0mrFcPk5$h3hOQm)S0Y+UjJ|WQy=^476?ztTRTlCA&w1J_ElHGAx z#f^i_U#K)n7)o1Rea5FA($$43L+DMJ$7{nFQcQKFnnhMJ2x!=$i`&orLr9#^+Q?^D z<26BrR8ZC@IIqI9DmsamxJtlgWX;MMI}-Sj<9abvD*F5;7)ZudokHRxs37-75Ng4ti&hVRX z4ubNUM2h%Rf?yD+nOGMj+MpY$&5{ikH(n{CCtm9UDDV%k75jyrL=lZVg4JWOkomY} zp{G-YXeXK5NM7AoF0r@;;dN;`dg}uBJ!htOloybN#)aZ}YWHcd1(RM)mcdD))PPsC zND#Iv!D*Bh9fViIS!TE_c+76uGJ5$Z^=kcd*yK5lQKYqLWJsS(x( zUGs@(vL6LMNqQXX#KaXYYAjgyqAlM_Z&*QosaUxr^3EfsN17x6@7M^AnCECCJ7Q$P zRH+}t^ow_kl3_{2jg;F}BFzf6Fci@%$`4~`-N3|xhP@*;1jAfsug~K}`)zcSyaxMjAK{G4MwSBP8Wvj{ErbXU<3Vcf}+YxH6~|7aWO4eNW-~+g3m>h zH7zoV&@UCgqXomTK&K9@uvHe; z9F|cew{&Vkd8W98-6V#9^oPKM+|>eE1%=yPiLXW#$%qmu?&6)J{5`m&<%GP5Lr4rL z^0|@FZILh`HQ^#FsvLc$Kps7(4>P12E#Uf9w{l%Zhj%8%S`XT42SDn8k8aK4DhVX@ zK9XO=hz28=ot_6GzBTyBq_a#z6KgbUDoro=P07gf85M#RL5*Rw*mbs$A zI6=Ymfg&>r-qLKwg|Gf7e5L4t41tC0Wd1SAb|UKPfrVYQ%d^HU#cf}svA!BAWCBQG ztMAua|Dx^2fhCu7Z%XB2!pahd_lDUQdg<~BsYH;`j5v@pc~5dl z?iqyI7- z(5g4`Jr1uDhyRBkUdQ8z9n|l1&E~xx~6wnxNf=<6S+81S|gd7b2J z!NfqtDk;c|dY$5cY(4fCqU!yl5Fa?Otj38E#K_HLkt@R9>5}$-Jd^Xe5oA5R38A8& zbw#NTyAlTSk60K`$$9> z*=$`SU$u#+zlxdPOLYiOU$p>kV{iH!YkS&T2!9W*|8axsKQyxW1WMAzA>!C)S1cO# zrWLXROGUReqr-w>_pCilU9mRz0b5tDhr5+`+@&g#vJ#?u(xOB&s;Odn$Q;*{&+2y! z_Xzih{R!dub3zALhOMqjMt`+po@h{+$kGptL(=Kzzb+^UzHP?1WZ6J{->B7ZTlC#` zm*=U&WZrRtApE;IR1uz5KosG>RG|t6MeP0HSX$fla&Q+-Ggg~S&$+sdLzr57t+}ze3Wq8&{$Ry!whu5PzMt=io5xT-HWY=s*9q<6h7Wkv37 z32m#Vyyc5$`gOwKQ^-dsFCdwX&IjC^gsz(e7AOy0`cT->AZ z_(TN@nc>@Zj7*qNQ9}emZG9@prQq5FIQPqbSs{fD#Wqx}wR+0+F-m&)5%Y87D?LOZ z*VTxQbvo%bBAZ9|j8_soys9g`gU+Y=J|0PfhGHSTorcH~;ovNa`gnXWhDg$b@qrV> z3T_X}UZVDFsv8p`6g3y#n2>K>&P~6>#wTH-PNU9gjn3Heg(2PocB|?pY^;Sln({ka zt#E5wblsfjDGoNLQv=_;+`b*S8xvY#L9A>WGC7|k-pb4u^@=Ou=sCxBeHiAfgHu1u zT@mMI(z#_<_^W$-MML$sZKhm*=mMxUz^{@(ku8Xnautv4k#^!6b@Jxo?EKZmc%E;= zqL|1Rl8gS#(G`ej_ljFP?nZ!BWh)Hz%S?V8wOq(TPTS#y69(e<2$V3QBQwI=t2KI- zqS5G?8<4tZUYggl=orZN-tip^g{z}6v8+5oi{}4J3y~kV0*BfwXXKnXVVXz{{F%gjpu(qyCSqQqo?^`CAPS571z>h z%*;yep?zuGrH?Io2Oi1wnw_Dq*lC^K(Of60M$avqRFoh^F(dppm8DJl_) zE7;DrI2|v24oqK@cI*U%RUL29vd{bZ3EO`Kzmb<*L1@kp;yOIcl?6Tp$?eK0oqX{# z&GYgZp1~65Ju^|*oS(#uxLJSpI-qLMgIK7DM)gkefq^|@8myj-bu10 z=~2ClTZ=R4vBixTV$&y(vaT__*6>oh$zkygFoe85HR9#8eQfMU9vkb@+mJCuFTC(2 zNcUc6a+v9X|L)&fN&GF~VZBs$!{2tI=<{E?nmRIvUFUD%Aa80Y=-YVB4Ht&0*t90_ z3uBVd`RnVSU!Hw+@e;-jqg#3`l6q#P@=f9%<9BV>{u-P%5$m)QEABc$Ucb0*cj=%%iJA<;A4JFkK3c#P0moLCwG+TIgZAV=P2C~H7r2JGL7kNS=n|auc`Q^ElFkL2^vWtvFtTM8`(jNRo&o9q6-k7q~_p(eW($RcnC4$EO zwwgrRt&MzGd+!?d-U^~z*BRh5!afHIY}waZ{440G7!2??uX@_(+seO-eA+FyGK+xod?jridllk*__F0}Pg zJsZS@5KeZ!KZxN#1IwK=cTbmvtyO|vXgT(}Kf#k>w1~x;4 zldtj3D^C@t!4{CI84oR_n8K_L_(xX4E|7`6q7g9m%?K&?URY~~ z&>LB47r}tP50b_D4^WB+M`-YkG#rnFV)e~%12`U<^2sq_G@FrIwj2R#h@y(6$(_aG z+;p}9LK~odKTjAn;$oj0YYP(pg0wHHtRNUm5h{|4z)E9rSs)bKC>HiMf;L4`yHJS^I|XlzNGjG~c*HqqF^ujoP{K8BII0eMG40$n}D363r-~ zH;JMbkOzd|B%-|nb08Ea5e)?B%Ly=()#O+O$~k(g3&n0kH6t5-7qQX2YF zJaT!U@&Vnw9Irw=UouP0_`80Hb+8g$(Eq1f;KC>nP%I`jKk@Z$)cN ze5>)2E(AD#hTv^f2#Fxt>KJil6F}#PJ*`0M3}R=>z(bfMJ8p)nOM>$u6-%-)$d-m1 zC6&$CoF>az=6Ydf=E{nf_a)bwVFg82&J~f9R2Wdw@G3sJdzt{Zhu6lSnG@HQt%mk0 zNb*OUC8*d%s`5jEh%IkHGtB-D=kAe)B^?#i$e4a8lLh4@uurlzhM~K*v&ghqVw`m) zX_opdF~?5qNmZ`;EHTcyl16g}?#DHnQSQhjl0%jln5!g;!O4YNcCG{NERVqhu3e*5)+6w?$m5pWzr2DVHS=v&D8nP5wVA>mv!cNql_#YMGy}T z#^ZyO+41#dtlC!T$>`67iA?6W*iF03#6}t+y|w{_7A;_t-qw@8W3YcI9o*<>bx|`f zwicLcG1*sUNx;r^moFP-<>fl6r{HsL3oIFS%2cAC;SdRovqg1miH+xMF3aVyCun|5 zJlN{iSZYTgk7rd`GNFs*BTMFf{9=)0!>c)=zWOX$$h-`eEI&?swQzM@n`6vfCho`` zEqi0^+pdj;>zv$oDKz&{YWpa)eU#chN-gHvN2%?j)OHZ1 zhDuvYtXgE}Yz@UaQ0?C-PR+tRpJkb>gnlb;SwsNJiY;M{0+az#KwBR|{OCJ$tVcjY2ue6CdQ}vt^x>%Hw zW;3ROmJcKS)t4|o zZB8%_JlF{FrX^YAuKe2rVCgW5vt(krI2fz!Lf3uqZ!B=$A^~EOlHtCHri%j?#bGn`Mcsp=nFjUG-T&d$TFtj3_Y(Q%SFGB66g$0(SsEsYO5dQ zX5>A>{S6MtkceNhDOWlP))il9E>6Y86tVH z+3f}8{ml2!w}A&P8dS7fm6uVuQcakNsD?Eg4maB7pG=| zieBGov(h;{PUO9CSTTtA+gz0-!esOn&2E{<5!W3CCtnIWx5ddl0yfK6Xb(inqG=k7 z=(xIOC{}GdQk6<5>T4bz;y(&a|0Tc6Sd1)cXl~nyyWaA8Yu#|u8SDz+@80hVHNa_M z*h;@X=Bh`nER>t&E})`g;Ei$XbUx6_P6yA~@+Pj@*g<)dH+k3_noC8#Dr9y`&KU^p z?*sU_yvcV1Bew@3`!@g~D{uPs?2ie8Y)+>M%Ml*;MhDoNcni4Hu0W<6yY2Tgc|X!} zR4F#%y|Hgm9lm8t2n3AovVEbU`)LVAUbt_BjW=JNf2W4*MuAv;!gO|$vBm7fQs0~z z?Ra?RQgAKH1VncC2A+bMEDBrAO-{&jwwQU6$;bpd?*HSWPc5X32go@vPdB#}+`(NH zES&PDx9IC*4CDKN&Db^g)}sy6?m@f`8Yo_20#Fl%%aZO_R|D2XE+?o}+ha+TQlt2JF6WfT!^p0_xC*)x+nFm*hgd=Z zNb!VEE2Fnseeyk)?_U#%KA!UZ+u)uCb~%?~P@|v5OfMWdK9YebCKLxJU=Yg!q7=j@ z`}_bDtNJaoFXfaA#~q~ga!)Altw+0;gFbpWNU7pN(3_&))7o6n{uT0~pi{!&kCU7$ z^M7)E{SrJWl^BNPwTbVR|N4wlWD48wRb+HY;0(b#gp1sCfvHCQIQzt4usZeWHyCVm z!^qni&u*^*=ZP%MJrUfBHsU0_7q!l8x2{qQ2f;h;#Bze!wH+n+jVdKGPKN@7U@d@^ zt3JkZIL5Z`9SA$|9YZ;)v+$)mw>k{ z7^M{=&CD0tC#RfVXIvf ztBNX4<*;)i15ZxB`26!hq@7hXonf{xjI`5|O*QO?j6m8+$)4IUTWYsZJA>YZmJIaE z-VAh?TRt$JQF-Tyx!9fA=EHg9{dB_|^POgoC*+KW+2Fl2V=~3pbHE3ut1CZzAoIJQ z|24D1cY{i@DHcg80@QFucVwn^d{ReLR`#xPu@k~>L0qP3Vt#3zIodDXq{APi z7}Jvs83*+{t>zh5H)>G4u<@96Np&p^4}4(ln8dBqVH*N|Rd$LRZFahEuUDL?{`Q7A z+g-M>oqPP~uaiMJDnz^s~6F)}+C+<3vM+B*WPhsc=Ny^2RGSpFcT z4dLr!ilou{RNkSl1n6xrJ}4cm?D|YDqDIFatk#a;$I5N+K>P@eJ42iU1??-y-mED! zt5u<*MvFq;tHEZ52f3Md(?6R|e#6L|m!&MNHz({yFHML%F?u2^Q8g-Ue8M%;c8S39 z(~?kuiAF=V0v8wx-FLp?8#beNTvjDg){;E2KRq#LaQoT^M+bjc*| zLw4+!8;2~kziGMA`~C}_Eicd0V+Qa&clszFFJSJIuc#VegRiKdQ#3LhUSECn>d34V zupV8WuX>(Nycl+zZzY2NFPqG;;wK|!WbV3Ck(J2#pgh!MtPqH#sCpNsFi|N=tHqPYy=R!^u z7fMr+F*RP&dC~HX)6>(_FFyYq{(pLUy7K=oKKuOh&wuyy^FMv|<>{BFfBNiCzdQZ( z%cp;O`a5#^7z0o#P0Qb%ZvCy*&V3};Xf*mHz;QGhFMFq%z&ax?`{d~-M*Dq6o>xlC z`5TLa#jLy_vyRS@qdm# z{ltcpaVg&NIa8X>7te^Op!gL+&@>la`^x;ph&AT(ScismJcT4(JE>+<2v)(rN2IwE z;3f{lOF31Swh3^%;7Wh9_R~vlO^u79DrvFObmW;_Osj&H&EGyDs8BwuH4Jm;+p&W7 zjqGULJ*CBBMxP=z0MB&ifLt(fc6IsvXV;C}E2neVU^iD#CW4Ec-|@Vn1+W%uCZ~e`%hUD&`UTZ&&46sQ*-|*??6Hq-u;m0R5zs@6 zT&vY<%C*~bHeOmUPhd*;O{Hb2PI7j~iW9}BBU)xNu34t5lAX{6A3@Uv?7Q(i|Gh`$ zIcPcB*F+;&);%*|)^?+;2s+9AK)W&kZ!WIi28c4Vwd_nB_ds{7r@#ytC^6R2U&=Y+ z42c};lR?%>!PkR&RCNQeU=D-}9GTC75rO$*kt6)vr6AAgoE6V$hg)Zt6JSPKjm*Ht zO+m;*S07#z2pxOtn6K*v%g9U?xe8;DGGAg5ttH9`jm}z9wuG<4dJngLQ+w!E#A%*$ zbTwXQC0#H?SZ~qS8?AnF*8ZBaSy_K2)OrGcwb{xoLbUZ^*2vUBdmPWQoT`x1DX8J_ zJs+Ko$5)%Kblvt~Sdgk@gf13XQq{7I=EE&Sr%W)cI%SOn%|?D}51=vmNs#{|Z#uu; zLK+;jGkRwcyJa~iRIOK1m6Hk2I4#IkDd(ENmliIUxMb`L>AEhL)@!W=JtqDodUs=#E4X=E&@o+BjTB9rq4)n-8224a0& zr_xz^p%*LPo@g5?V0$-SH!}5G=sIKF8qDCWjY(;`@jZSu91r6FK%+xRhskws-?EO3 z&H;l#x1;MrbZ_m@sWmVz{Q)Q~_=6p>CP`Jw`~A7yR+skZonC`Wito*`xq&0@IX+d( z+4o>IheOtEJ%?JarOngjU578}3v68z@bSAK7K9QsHac|1z-LW!+5;4A0-WIw7;gMh z-g2}AmAE-Yj=gEG0Xt;7zoFT!oJ{cGkMc=8cL`Mr3S0pv#mbXj7Q{y0&zR_B6U03a z8LlfeS}2)wam7k5A)x(Fr>ApuOy=|*-s>c3o}D{o;N2UBWhUi>JU!heF2;X2+5r2C zzT@+1?nW1jHM>aI65F-4VYu#Ma=Sz|zhR{r8QT~LXJ#dcD>rN+2du(VerHx>wE)~< zn8)Gn3jz?9)0v&Cqg3A+3cGCD=uEEmiu)!BZ~ivkwY%X%~CUUWLCM= z)CkS8YTjQj0L5@n@SL$9$y)uYYtIe~+d$?A%Hs+kxIR%^c6;KXXYMK2tIh1z_u#9? zgxxtj9I9=HxDp4NOsb-@($V1lxnZYQ*^GM#}rA!=u0-2n*9vH^Sfxt3tq%6 zo3EPhc-Y&L(cBWW2t}K=ioHXB*-LuEiq6xiw%T7soMaJ9t9|F{6rgej98HZp}I9x8YsUv(cqF=0A;^u5aN{STf(edAWt2h1Q_Ujr1&8S`+e| zE<(?X;S=8)rLaF|(ix)q_ipINnZ{j2<`peze`T#S6`sUTdt)-89Za(l zb$190bHR6rE!?dbli0z6fb4kRwq=9(R&CU&n`9l;J@g(IAP8FZT^V0Z#&Fp0-n@iZ z6$X|--?st$usIhmnV9O?GxBt>)mx@!72DiDK;b9zE zcrgfOnU%7d&c?pD+Que0Lg<?<75AH1CdF7#?9hXR=DOYBy}z;Oz^7t1z;)w|?GW{L65w3zZOk_4eg8KE0~C z;;tGKsM-P(a6jv*rsryW%S;wdvN1b=E0krn2_MO5G)sGKXhEPn!dJ!A7Mj+Hh5 z9cQKA4p|fHRpIw`&4Fug*W4Qpf@^Qr+}kzRuDxBex2t>hw0iCBn&I`4@uoPtChd!O zAnQg11X6|*z5K?jdBx zn(R6xtD#)GbD@ho*SCD}J=jm9`*&^uM6oFiQ5Lw?prjhNqHfu8+hI3c`GXqu zTeeJ$=vY4q+xizHd`I+QAN8@1`q*y#_E8_5-+CxuXmEKYRki)jdS20Q=IbB(T*lpT zyuqGVkvnV=NG#Cd+|vNtCjf$Ss2((h6e9BXw{Ncup<}{DR#cE7;yj&W^2Sd3 zm|VQ0^F_hxU0lei8ZAnhk193Fn9wCHp8D#Af@!R>L>K(z?&-;*QnOAJtAX+BuT)#L zMm=m)s1`7GUdpNRRSmC!Ez5GCY|1r*Os_gWCuf?Kb1s-ILlRVJ_u$$st-hx^o7qU5 zD{P8`PODMzNj78ItwM}~x|n`vZU652Ed8h4+%)Fm-J)bl)zvb4$6@sqEXijUr5_%9 z(n0>pLdl5?+r<6H1({fIku9Z`=u8X&L=`!+&nD(hE-Ll zAg(wkCh!3k8uO@#-Fs8`I*c99#0V$@!bulgBaRE01WlV!PO(El7mK1V(Tsp`d28v= zjaqc`{V+F*{@=3YfwBN}Qm7eU4B<6Uz;%=pUj_4fkgpthTz{A1n7o$S{O{r&R|EDi zGneO*sn=4&)7w`#ho(gjjYZhuP%{;Hp6Op$CKGwgkksslm@9HAj67n;z$4TuOWtYL zLW&WaFZ6&=ir;R~$6+>w&9Yp3P0MT+H)(%S8S>Y^F zCOdZ`n6fgM6{uTWMe?M11*C>36NQL6LWi&A7SN~N}MFG}r2 zshx>ZXw>XVr06YROL@U^XBEYwz-YBf6^hhSWPKG(n?5ZpZ;M$bOOcVBmvD}9rpOJ0 zg1^>yX66iF?m8v_pLYF_BcYp-gB}VjasLpBI z16d7RTjD>>nJ#%Y00GsD|Mc{;r%ylsbQS;Ui_bsZ<3Byd^)ccr%{7OjZVyeQUj zp}0OcP!{{CV9w+iphVsCqAgYeVU#8nJkx^-^`?e^YW~zh(?LMlc}W3*vZK~Q)Quwo z(MN$lt%scK!JqcvPro(rr#XH1!`VxtunZh!ul$3~g)#4_XKglaXVqb|`xV5CDUwk^ z*gLL#w&D+GFI^dIG{sD0nem%qNpe;&-DXJYnd&(gkzMl-`mS;D&|S-cQs(gnUUt1xRL6z>b7d#m(nr904r_68%7v$?*w_nRn5}Q>TAMXgt#kjx>hmd z24gk|1tZ+Kwpdf$8JXI0M2@FbNXrklh%Zors_fb1Ipeos)_kvJ?X|46)6r{L;pw%k zwNI~QCB2q)Rrq=>Yp-Q}OoKH=5UotG89L3YFk@3?l8oc5WJkb*1zi&@dhF;fVzCcG zl8WezJ%!a%SUrVhKRtz2`}7nR=_#yL;p-`^p2B)O3M=Llwz62PvXZ{*3IZ{C-(@sd z#ZX&hVzRHiIIK$eRVBafMPYTI_JmqbsFk0dP^*1zuInyNbR3yaXx2svQ;Rt?+ebEha zD4BWqVD+n8c$+XG{BV}2IDL)5Icm{SU7z5JV0-Q4x^A=3CR*~REzB#*v=J~9n)vR1 zD(0Q2xvz9-6SeT|`Rj+!P7zI#TOKzCd#ua1t8hfZ;v3&(+OI8>Wj0?6lIAjp71FLL zP%z!xqbGsrBe;HUCgR!CM{xc3ir|_iEN#+?NTvsH|mhcF?s|IVbq1q(gGLOKapqT*BJz*5WKCIRgRJ?V`9L}16LbDF^Aktu4iIrV$+{ov&?O)x3ixcMlDYHPYsi@hQKF_un)p;hRjZw7ojEQk z%Dqk#^9G8-CGz(C^#e{52BD}=s9);YBCdI?aKj>>_E4_Yn|%pj<33bLTKp!GqEFh` z^7Kg?`>bfz(qtxU|Dfusb64efnO8!3WNy{*qNSQ_D_$}cE5nKI$dt+7eu+R)6x z4_~uzCgx+Z`!MlRyJ?peYP1x%Xu79{W^(Q;<%(i{w``c)qIL7#Qq3$>v?74cW1i0D zBl1Qey?YCoKt8pCAo#zt@`MHfzfFipLnZvJmdVvFZDh{tLxjH*D%|-63%z<R zRjgSIWd>2csEXp%cmN^L;4SrydU=vgr!0Q34XaBd>iVL)ee!{-pobEsiwTQCGOuEh z39f)^98kn!lFVuB=`dxwdLn2nRM0Xn=228<>t>JA4+Wbw^thmRUs3m(Pp6#+dSa>+ zS1o2Dh7fVIkvQu#{|kC|&P3dDT@?XSRf>RRYausQO$@c{!EsRB;Dm<|w)uv|kV%}y zF|R*2WR;n}z?D5?@x>~7l#^o3+Tk`!P1L@E4adUA*-RI5=dQG5SfhT$;4CD~JheY! zBeX!x0tHGsJB?!&x*axS>&X0Ys_ixbku5(@{269BFD1vu;QKdz%)}6XIo4;PRRomA`l5IsXpEj7z zkqTyAmN#q{T`&*OmTJhtNsUw3-GaE&=84&?Cci=DD9)j{jdioj=Xgpc0S&LW)R~;s z`LyWAq&V@|l%|0oZp=KeiwP3q&Z!@EL7v@)$pf~3e%gJUSYEG|9H@Ln9S|j?yU!v_U(Lu6;H$ zFx=FQU}dqmH_7T|otD0eSIXe z8B}?lt`eH>tqL@SdNY{rIZuR%srPsouXW^UN6(zM=dbl4)AlilKdhAR_0ip1Aq8#b zIfMA}$o6#$)&E1ep0cQn@#U4P-a>dEcvZiWY8=Y_L(JI{q8sb*K=o#-HBGj-vE}ht zZWP+l$XkYV;kzVevd7Hls9{&=+y!DwxvXGde%Nq@5&GebB|t2YCrD z^RKavCErpwXBe!ErAfr%DUTNfZJ)sxX2$L?GI*=W5+R4m0?kjz69vVS5lN$n#V$>l zit%)-|AKDYE;ogXZNcxCugKo$>5FD)SeP!FCv4bhriSFaQ>kmlqLw1xwHNVX-d5zW zy~vy<^966d4Tt1gn#@mHOTj)~(6%li`20z0F=(yBAfdSqBZc-lOg*&MVgAkTY-_J$ zIkWd$%DwGWt|LXZ;9eS}E#!RO#|LY`g+s3mX)E`Uq2GBLw;Td}4>YHW!AGJQD!{Af z#j_5BZu9;jh^kf?^EsQP0gVYW84n1ZQ9n$C%4-C4$DN32O*(J;fgwx02dI*HqZ}^W z82x#(ncD8SvI=1>SlA+3EsWIEQh??MN)?*#f)2rmR9zFyBfjQ=N^^-5k&NU#v%8iy zT^flrW@HxgG{USl^oIHYodhgDmFv5YGLZ?3XlqKJ5?8M35S6QC!o)3OA-J(o!9psS zcaL*o47cU@{`#c(+;kluK+%-xq;iOt=Ai3*tBP4yxjVqtoZb0}XqS9q0}%Alhj4{L zrLxLRS7y0wJi6M>npr!bC)9y6TRPvA-kB{;TKiV>Lh3MV!NrbAOys?#sg~wd>JUq! zF!Qifrz$q4mUUP(5AGPrt+lelc&Hb2sS?_v6p8F#92_5$fC<6kWJ`H(CR>EIix+NL z5D=P>H1t@UxIAvd?u0~C_7jfD|Ncma|J)z`&(+WB$IkGJtDpb8{#e3?F2Odm91JFT zh2sF_A%C#B5Mq|3fiStMvnfL& zsYzE2^0GcbbuFlUcEvusV)^N_E7m@Jc16->S6mgoKD%O{UGdSfE9S|#dr@!oDXa74 zDharXY}S45>S|DX>Z+%%%1=*S)jmCSMSALLRrq@9s;90Vjk>y}!DXC+9R6!eVdYj_ zaCx(r{nw=`Cb?578HMT5odF*ZG-#}nyHBT#L<%YH|1P$1m@M4|{kKb09ew0m;3RSQjh$I`Z zkR|eT#n&vB+ZGar%R&`nhKM{tpr)}7@+u>`%H3Id3AStASZ)BEa+Uf_z4=rBe8x|} zul;RzPGga5&oG1Hkj_{rh8m-8yRnySt8~A9I2);ABbP6(BJKrzmW-Bk5!Cjvv$M1F z;@LC!cV}m(@bAm#FQ32s)9$n9Phal5+fXn&vS+<XO;SJbSPYHpu5WfmUySw!gvd)lR=@NYED>}OjY*gW z!9W>y6TRuG_vAocVX|XTosCINDQrwbD5+bXg{vNt{273kI0a|id(CG^3rObuJNxZx zUs*RtK^oJ*fQo3*4?%TpzlHn;n4A%`Jl=p1H6Z}XP!0KJmj=y%Wc^6{v?%*@wUfqSg@V= zvmuS$xi6S2(wJ@2$R7geAu5I!-de1VR>8ouhPZ$o*{}j~Zz_3>#nNUItt~aqk1j5W z?iv=OIQuZ~mH;7Jf(1}yQ=panBnw`c8F(8{m!d#0oh*Ej*zt)+~x!A9KQe#R{vL*_&2?fydT*cPDLAw4|KEZsj;0v@0jU2A5 z*4F|^BiAkK51#guk)*i=H2Mn2 z0oB?~`5|7ZG(mP6g~&0J6YJ_jT`>^S!D9ya?$hjMY#h~VCA>D^sH8!J#_p4R!6&hz=ed)AhMBd)BmGaQNj2T50HI!zZ3(obtd)Mk4Iyag(byxhS)FSer*%Xw(kKd+02+DsL7quW z-ae|cw`~_LJ8 ziJ%k`ae-e20)DboISf#vfnOTiMPhl*;>=W{kasbNw>y`Ogf+cTA*kvm;LT>i za-(P$nW0WOd&T02Ger>8?iZrxTw~pSF z_yUYXl^uTDa^&UNzEa7P4L>B4rC`Y>uwI}N#2wXQwNDj+0Zu$*<1f2k>}?W|AE+!s zm>z{B>2&Jfk&VAxPc|Q}`VPDm=kblMfzvo)a2YWV;3wa3;%V>EE^1gk{ZA6iL?-A0 zusgt&=?X9fQuC4kE?xLxn&fx0h6fCSHO!s!#kGaMhh5%z@=i4;XM0YW7)~&epdAM# zax;=i6YFd)Sd!Edp(_jrws3c}`917-7bw4ujQ7|f)j0i@dOgQYtZ2&a1BFr1x&4_k5jn-Z9K%CY}ApP1vUF28g?-y@Q zTgXKF=+(ka69P?vZIKHBImq8njxHde471FxB>87;<@#MH@0vW5qT53zW4yinEAYKy zKH~ALl5rp%Ni)`FN~Q++N%8?Z7g+|WYqs20`R9-j^1W8_UDXZdQqMnfw?0(E!nFkQ-Yxm`)u+*d1~gy!Hi)X;3-v#ugP73Nh{{ zOL802C_?g6jUJN%lHXD;7??QtZZnL47_{*Yw5L7gWOHb#p_-E~{;_}Znk3C;T@ayS zCYf}asE{IZ63k!$&&M4wMq2{tN4R|S7SY#_KJ~4)H(pVA4Y5%8@nhaZt-}WnWj_e` zZ6}=g+7#E8X&BGJLX2>OVR(;534ytkl=r9La^v`+8>Dbmg_c!g!UQ(nZ~^KlCa?Lm zkC%!@&cN2)Glog$Ij%SL3Uqvd?lWw7^QsA!cWqMd4u+4}o}~%=%qOb0i`6ce4&dZ3 z7R%(UYSIP0W(l6~#uVszjIf{)s_wXlmP_?3(j*hATPb-1=nKqG$obKKzdb%bI)pAW zg;*o3xtL5!fg+*O7Z)hJC1Llyc0ttM(a|6D(z^ia6i^FSknx|0(t8v zg*=eH(0=ZX8JROd!Zlmg-XiTNv?&8@K`F8Dm6du-1sg)m0$nNQF&83WAcCx4I}M8%C(`0_Era@aFbn#}ck99^Ma5ldn%6M4hpusfGAuay8)T(*^+7j$QypSU#kjDS@r6^jD8PK-#i)sz595a_A zazPbx-jPCOevlosMY&LoK3E3U>vSGRrvsZ2l{ya>_-keIO7T2T(R4yGZK}Gy!F1Yj zBoig=+$@?mG`yb1FxTvrmN8!;hEK@cPXwREbkTf2-pYB)lZ3g@KR`VH$|qMqjH2qc z1=cJ8%_HP?tuq|To`R0$;Yb#rbLD09@G;cbrPPeTIb{iisKU!ADww2zq?5U+V1SsK zH;qB%fO)l>jKK>US?hTM@@2odO?C&7Le*}8Ot}ha(j}R&v1~$(n7k*$Mxil2?>^`d zfhbKOKTbK{A`=?_EE_xJLS5&oBg^)&vE2zn-;|5vZy>qv33cawDDk7m88SY~zDM2w zX$&%TFcoY`ZX9|f;gVHyja{RPuFlZE#pQ9#ToHo1%vX}wkJ&VBrn6*2f|G@xfSC>r zmjZ%$J`iuU`^vRk>+R$kUV4use8`h`*m7nCRVQL-k(r^l3}9Sstp#?d@()c17_QI= zDq47zoTy?%4v^c=(G|d2r^y`UuONzOq@JumVxaHPf7{C4e_Rg(JMJ`_UG; z4b3Y4{~I)&BKz`UM{~E!((5FcCC#wbV`7VkT|jiIwrs zA!Q35ULdKbZQ1pBl~hGi383+AuzVwr9JtrU(Q@Xf3CIi4IPK7O2<@oT9YRnmb)sob z9}Gx;a{SQ!$&u9ZPY(6GpB$9-z-e@mHf(9s6gQNBqCN2KjHWZTWoTUIXo%g%Bs{h+8{r*(Rxb&BQA48=riIRbYR^ejX8grJX9(qO^>#FgzI3i)YD*9Nh^?oCix1 z(0In=oF)q}Nyc!3E$xD}a*Eyof|G55l23l045>OVOr*&0~Y(DG0s5>e~=kV1Wa3iO01S2^`o)3``(S1rlvP2*#=h zP=sT*)a!y*@~f0bAe|0Cdm;vVGP=*&1)Ko%e$a1?MBB|>r;ieB)y(AT3%iwD+1#SS zD)F=}{e`lR1H0F<9iCRt=4pzSCQr&%+^}N+g^cQ!z7?fB=EhjiyJIh4hl({_W#(U7 zv3Dg|X46$V^+CI49kuny21pVrwn;R>$&91p^@vUxU0TArPg(vL1pvSVeWE;jA_9Q?_(E4m-vvGnA|?GS`~emNu2=CzvA!%Msde zo85rdmD+q5-~Hhe*q&`^s3?<$0B1eQUN90Net&-uQBN^5$JkHca{fK zaQ(H7c9sTI^8EEoawl0p1;^i@N^6${RPy@`nzRSV0or9)X&`(_+jUbr>e6brL5Qpb z8;Js@h(tdGmeVv{JG4o_-D{nADWVC8*dcPX#{40>Q_|0(*#9AV!l@r8;&sEIO9QX- zQ8~~$3=*>24kOM+4Z1axjpNZo?hAS+2i9HaenRj3MY_;_4t%O9eFdi^Bj@ynjmUnp z6qntq8n`Ec;8bY8*mjb}V4c|#T+sM+8S!GBSc8| zMRE7?u+`GT>=HF!6<4MaHzP9*wb_edI$h)4i>Vi|#D8kzA{pCO(gZgF5J4 znmq1Ww@gIc>rNZyj3P3knag^mvnpFmnAahbl8&(#8uMeg=yIE7vIO|Fmv6h9huv7> zdyv|SqU(o>4)cKBgt-i}xI&%5kx5@%UgC!wUhal@fgEw_zN~V<+mG~ znTZX022n%aG;dXuQek7LJQthaf5lST~3R}c0X4>&B zU*&o%go#xlV3r5gN3gXFdh5jbt>zc@V9Fb!d*lY&xT#LwUR*+;VmV2VNN+#%!Y;Z1 zb@f2CY}Ku(5|8Hu)P8od#kl|ZfxyfiPY`CE`j}@(`#=OH+)`%835TnmN3TD0ZHKGj ztn`Pj=4shDrQv+9^yVganLs}T0rDIe2S{~#Z16i`1Hl$i&|vJJ7s0E-4fOqjhJKVn zfTViCYZf}Y;tdN0MbI=-uzh zWMi$-k>k)<)islloP<1H&>(AbnueILaEn}WNRKRkKwjf5eY-Rtvdh?CY{7qDv#G#e z=l*Q2ex89|4{f{U-xm-;u1$19gM@cWh4}GV@1dEk`=8;E)~Eh0oM2hBUWK8xrr2cd zyc{2v#T+E^kp=fLVjnokK#cvu8Lw?st1peypo!7H@IlVErR+B_G45+VOAa6e+eO6O z5rTYN-@Nv8Oq2Q}+XayXZTUiL@Q9K)U5k+`(+ zlK4~g2J?MPqXXg+@D^G1))8I6l}SZ$tm?kP5a% z&eO?K{rdXgFME6X-~VZ*0cD}*J5YFcckikB@jvkE>C>lQm>;J!9@&uCXssH@pyfq%mg=_vc43UCV+LwWrP^}NJ}Oy#7QvRe}XxS~mGa)Iwi z@`6bo`!iosV~HEn$cj=AWYyJ`SVUKH99PIy)x6Do8`isSA$t~ceGJ#&7NS2 zoE+<2*oB17=(vc*Ae}RYg;tPTI9`eXtHsWKLY}CepNvQvMJ#q{g6w6$Z}nd&;L&K> zT8r9-zkEgZMo(YV-3u&C7xl=#c`k-ps#N>^nz1_h`H+0q^wEoX{Q$H;OTWj%rjK)) z%omjy8^3=`lle)*ci3kOTHgU&948G0poPW(m~dvzZ8T0Fw9z<+X5ZAe(OSan#M%;X zTS~N29SiQILE0_R@d=hStdl~Ka?SN`C6}qkn#5ST)Ex>%))PMzFP=3HtH}s&CFOZZ zQKvjkF-J-_nVt#qV2mym)&cCYG5YgnbNu{H%!gs>NEdzTW zD^SNOd)mls|8w=T`mrno+!J#Mj;u1Kbd>M_XY8x$BTjUYd{SNd!nd|x>J#E>&tVCW-BCTesy_i+H zLyoIr6+AJ4%6z56j4E*LY|(WSTg)ommOr}jT<7_R25?j;95b&?0V7R1Y{ax%7_S;J z5e>6s^eRL2|$gf#VwzZ1sPyt)Bn5*)I3jfKhfKM&4{s^eYAD+8H z&01&h%`|60W^o~&po9cY{@jn+Wbh_dp}x?+2W+pK$NhI4ZjnBmvybr#hB%|Otd1paU5of{e&EcgvWXgL@`6W4t)~xa0n(L zo$`g)?c=n{KJ1yZyK0{D92f`!xt#kNK+KiRd zh;tk|TP7Y|?ogV03g}M|7k=pT?F0Eob3*qOpXU7UJ3G&x@8t8p!ykSA_fPTIx!-Sg2Q-SzpXW!f zkM=K)MteJZd&8YChPy8>cXs5DXV3pb%EP%Fy{-F$?q3_IP&?x zckM)-_;TB%zTWm1v+aP-`1Z;1>G7MZTONB08efm1a5lJROZj&)9t;WW*TGP4@xf3X zgkkm~c{&&pH%Jq~;=zLY+E+(BqE zc-Gd)8Tzyls=~#DC_9AfTvIQ&ARX#9)z|5{svWAWyO#j)?PF>##1pxl(?-qT4U ze36RG;BK1gjMuUYbL?fK@>6BQl7-1`Rehp~$K{z;fr$}VNeHJQ#PC>K8>oVZP;RX2 zjRNk{02;nvB9m|~=!|XQKbLfdR0sTZmIlEY4}5nyg5dqe=udD+vg}fUMr~_r1E?Tx z^q4^mK^`y1tP*JjgUVS+mX-aa21v%lm~O+zDYL^-myt5VKwE9y%C^;n?kOSeoVnLYx`0!R8!S=U_AS_F zTtxy<=7gbUSgc#x{3 z`dG<$Tw}fO@q2yM6P~Kh8|q^r)5visSPTZtJQg|}i9`fI7?F#o5GFfoX>E-h;NnUK z#CO>UDoUJyui0I>!2!Ute|CJvV=*Sq7dtyUPM7Y#iurBAVsgO4ga@n=4i~>&tO|~5 zGXrTCL3#^O`m{&|vErfGrpX}tjU^?XL+j6Ykc^ew}`+ zA*9tTnAAkv8|wCkQOqECB@eeH&e!h`0-7YJcr7^wiZ~6yP}P-R;nq@MbcI3@^V_PS zAv6RXt2gC-?b|QrT)a(Kyam6`Xp%_Q0?v>tQOI{}iRSiz+`dC`@{ENn#(0equ|a5f zL`{d*#ew@YH=`2zk!Cb-EY?*sgfGM2Qs2D>q&1wSz6S(_1{20$Luv-~7GhQ^O(;?W zIt)IKCE*nW{1#=Su`f4WGL{Xmt&#TxP?-FXlVQ9kBS?Sww`;ciQleGWs{91?#>2{| z?!Ekk{prz=LVwloO@Fy1D%LB%SM^{mgMi3m>GKe-Z#4tPrwB1Q_&RCLvL7%>vZy&Q zkjcG}y)vCjQoyL^;OYaKA(6Kjg8+bpg|bf%?&1_KOo;oRL_G8`28Y(|>Se_FHYi&V@N4Sn`Kxz&D6 z3l+9D*W(%Y2Jpf32(eNf*^Z{#QT@reAf5p`(~III1ac}9FAn|!jXuo#@stE?Dzx)u zKGU3)fP_!UyLV*tJ#wrDEb`%lYQ>~Z@r99{1L-l$AX9cek*t^{g!aRRrUyvZo^!5O zh@6^;U0&?pNCFX3+ap~}Nyt4mz%OSkJ_SN^H&+d(*NUd1xtN$mf$!2}Om<)7YOuSA zhMqw-inmDy5$BR-+?8PAtwj7Oc>;XPt5=%beL{xh8PSc82I_ToZ8;%L9I~c!p9MB4%uRLFJ4gE*3#zT{mlcLu6)bSuO%L zAVi)m>wppk)`27$G6>~+1phm>9NR;|8H!U#-g3ZV;ZK2uOe&_VFr128WX^Agf=lSq zm<=ghUGfFqD0t9@ayEw|NY)gcJ#?*Zhw|hN#oUj*VMJrG9J)vsf0c$_z_M_KLcwfT z#=s1cwNeZBJ1oiC8m22iJD!9E(_!Mz!UCjQBxVZ+$H!usV3waGgx`jCX{{ES;1s}a zl)ErnUyZ7=-5}|M3~`~-0#!9?!*3xU0rED@@u+C7&}FmTcsNCe3!>u5AkDcQ%9RP3&R{?EQYF!p& zshQP3#HmVru436@Ymy5F2vCZn?$k{X5@1JRDmn0wmpX%+5fekNt!Y;Qch0#noYO?L z>6yhNLN;d6o+Kdlf4JB(9vlR`0id04zB}5IAIX1`HxUaj1a+@B;UkvN6NxMWpGpEn z0N?D5$XClukR?dK`E{bROkqEXU3+BaL4tesJthOES(vkY@hwFREG;xPAi0TA$RF3- za2bRk3O<2L{+vAmN2{ilp5=yzZ&q zp*pm$B3yvbQopf;8Yu`zB<_KMz*e8r&_hmAwG{>F%nuc+_IUV2kdO-hh6PKL@k)XL zAjzfP2>feCW=LyHlg*J{EH#D<3qjyvi*i(g0hNLr=g5kbmT1CJ%XT+jbLxFX0~)%J zj1+2vC{b=$tZxDqDCg^3{R8}Qe3n(lICyiG0fPy&P`RvS1W1kgh~T>;M%5;wL-j{S zP8nz<{h)u3$^Tzc3AXry0Ii7Pho^fxJ3BC&2il^P9EY=5j@-D0z~SRAh?oGu!d~sh z$!m~@ArrSezV^e}wvQdaCYW7)qwjcZYs7 z46`|aVN0+^x^%t`MXgP!d(A>GDZtlMQ&ky8F&EtB!I(JTUS6JEbg6qg_XP|5L<~{s zGK^_BV`H*2g8yyr48ZPoJ|^1(LgqZt-quRlI#enBlmdlF@&;svvSTn@kdz2+1t4;C z1xb2hA*C_K>u~Z);9`M=n zB8nOH4tSV|nEJL#VBhnME~yapbxv=9`%6Kl3{DnzVvG1wBltoR0BGwt4bd6h2gW1S zk~NGfclze?Xl&X#z@{V>1HuR0oIkCi{$By{U#MHfjKpcUpP)!}SKhGd%{LMP^J-fC zH>wx3>d|= z4H-*>3bX7le6b=w=ki6A3O3B_Z$RkGjI9eXec-WKumxytcV9>vc5RKkR>ts=oD?5b zRTiT~r;;2~+Ii%8m1x$AP6oO*%|hsQew2GrF8q7><5rT<@8Q4jP95HTZ{dgUJ)h1( zo(SJfewIxyc?ya_fy|89kDL$cbIHtGBIC(lAk5yBD-v09wQ%cQczM7Lf z9q&(gEWm0kqOo`z1)O?Lsnj=2pfy?f)1jPFR`>NRt*t2cYN6vZ8jHla-)K9G73G?k zw6Sg=bzi?>0zbW0u}P{2R98LwRv2>PAdbM()wGWlpCwOpap%@-+rq&v?OfSwr;S^+ zYb!zigd4ZCB_DskeB3oR!O0Sxom;K9NiH7*)}re|xIij*?`<-c_lI1OSI)0|9(tTP zR$wyXM+9gvva4yZzrg3+FZM>eFa82=_MX9&Zmn$zv{lx%X2Kg6bx58hPsdh?41)Z! zT!4N4(Qhf0ckvU)o&je}%p5$))jE#$Jr7xd_tppbKnoWzhU3T?lOGG;330NibG9nJ z=4R3<6!>|tRuL>@!iUg}9 z!pdY=jxZ^bCKhqB_m>l2@qjrhn2;hJOh}FrR)NrLvzpv1%Th*C`3HQl009z5$`*(n z^r!G4&BB5z9q^qmP@XJQ9V08#1eDxU9jPZMIz~a?TeTIpK<$D$>Cn>$TglrJr4UgH zR9PQu9~P-D8nb&-UMSV~Jjlh9wU`ig7X=h#&!A^6s!l0x zInz^UWzb@o=_^a0Q6Z@fUZ8*mP%r$TXiU}-g~-jQ5s3QTqmNI6J|ZH$ig*MF89yBIl2T_*5H;d6XGnEP}3QpTt@Y(u+&gj z921Ai3sjRhpfp_?>t=JoqD=AC6E}Rtqh!ksUtt7{Ezf^=(^O_-wE`b1v5{p$4S^6K^R!O`i((U>@|eV2vG(V9;L zlG>me*B~V6BxFJX;nzN}&ehfQKP-1=)CWe0Z=qlsktCAI!GUg)SbUwR$zHRNO)udu;QFg}yh` zunaXU!wi;f^O2@vER{_zQOu`)z_!4H*a75<_QY>v--1?MMMIrx8-V3<)~{R@ zn%qIONkbAadZPk+XgDxIiiV6Y0QUgVugo=+7OHCYK<#^^7D^F(%J1UPPvR7OG`>o` z858$&0+IcLSaCbz9=I~7MwK~Wxt~fni_S2+=%ryyuQF2|>O%VeV)_Pc2=8IYmnX>f zJkhpPf|DoEmqZgF7I`A^)e~c?nTE#b8-34oKTi}H`=pS4M8UI<#pIx{OWXF)MxXU# zC;|LLw(w*NFmApA2wuTR3v@=%i3ZC`0IGEL>gu4*V4#wqj|W7zdBQ{A#38DnFxGUK zWJcXN&;fFV!Oiu?_t0cg=Vnv1lIjGiE3;ZOafen33P6D6vjS68dO0EhvU%BYuHiTr zgt|~#8-3MRP-Y{ho~((b&&z?Z*OZuf#_q;s1MlGX5%)H&?x~xR(sYQ5 zP>qXFx*ul3E_LUP2C@zwRaH_Edl41$0!E6N7lP|a{ym`80~U+Y3y?cG&Sx79;ot8m z$A8fIaFT7wvt5kDop@j$E%9HT?>>K4i2v|n@7aq!{==tu-n|=woz8E@(?C1$f@!Q{ ztnD1)oGN=dhkW=jP|Ooj^TwL@#;0gTYdKHlJc2)b&^D_XxqWzgfw!e?$_o;Eh1SP| zcki-1O?bVHbgT%6jL3%%BiZ3O1D0%bYB-V)A4bWIJ1WF|xa3;JiKD0+1 zx(1Vcv9q(&39!N7oKcS~c+3c$@KoT6sutyfWd_Er)D4?`qG^kmG-b%J$?poO zEG$`e+{HCfd*$Td>f1LLmshRZcnHIMrN*4!S&9Yjw6`ic%FGwBtK#;``MQ2^$a^p` zfANeAhaPje$2P~m4?Nn@wi2fQ%JxM)%8`rlbR^d`NUA`oT7$trqDB^avVI$uv|SY* zjvZ;32@{UbB%useujHG8^Fl=-GFgrXxkb8hJ7XgnMXy?CXD~SBf@N`3mE+p+8AO7H zDEgGRJdFY!K`0f*B+!aFyu#7{o91JO$7f&uyC45fC#lndKhE+0cqco*?2mQ22ZQ|p zniVWqZW-NsnlL0eCMvc_PP}k&_#KF$m0grNgDkGyAubhc#$%r)+q*AqOd=^6e+q(# z4LzQmG)EgVZ<39W3sPMsC*9eU?aD(rCKrC_GIDB{&3T`K5j9XKSw_ir+DdKmS7^+y z#SX|i=FS^1l1dGuC@y>Kzc;PhKjo*1|GS#AAY$+AigK4=u95y8;B`3B@}F}n5TK8(Lj?( z8eiFAM}QzYxH@=q`t|WQSBJ;vuN(`T7fL#VlY>{zcACTh*g}#oeu$n$WT*uDL(h-N z`nz1C9~{IrLBg=Zx>lSVK3>xZ?2?~gSUb=^QjBjlOajKDA^au*=Mm(1puVu8Q576p zHHkCGx4W@P-VF%3IQaHR@{GF!LiR5&k50}mFJA4CzmnI-C&!ns_P&4x*aza~=?UqjjMKPJ+PPIVX-%bgAt0HbFKf>o^&b(m_S! z(dpsYo8!~VvHJV$&H1GRaKK8Roa~<-zH;Qce;_pQX_C|YcWRETES zzPQ}Ke0%X~{r|yVX(;@F{7BaEHF-sL$j<{pJPf>Vmhl&M7sX^<|4sfc+5H6?P?PbK z^EI9ty{hA^@K25^=}%=(rj6lDkagG_g9`fbB~5V8M1JiN-_E z$ea_C4anM7<-r$+@9~hC&7gK*%;X=xuwMvGKn{KbepO7QaY%Lsc&_B3L4zC>{C;Cy z1%w=|5r~?nb1>y>ytc?>X=UVrfl7){BA#D>%KK$dYFizzuihSfC%3gcUI!49XvAJY#Y+M!6kPD+Lj9)#2}&W62#u>arZMruD20d)a#7e_ z6!@+$l%nSsN%#FS&|4ELz5+!~ZcqBtiY3>xo!`5F8qU=0s7ZUGiar_XM3@%x!O#SF z7fqFM2=xOU+Z`7xOR4 zMz7iC%GOrlj*&yiz}nV>YFk@thZpv7qOI#8>aGr)sP;qSkgc7SA;SEe;M!SV>vj~5 z^+;Ro6o9XtKvhnwOsm#VZ=f6yHM<9)xJZ+TVFbWc;YhG*B_OawW=FLlGppw`R5%1s zm|5g~ffyIm!CN*OQ{nq8G>E7|4+B0hjC_N!Hr+2q^5^Ej{!A1+XfGt8`TF?v5&pG@M7BlbVH%za2|;$MG?A@c|JK38XTuN8 zSzgyYjDAgcxalAp^&b_hf_1IURp2evG(CZGz>pLe$Fp3465FVm#)_$@o|nx$fKd(p z;4`M)jhB0oR@WaaVtw8B?u`5pDEx{>avMi71d8wq(JoBQ-Ie^}dv_0{#8;sI(uBqL zvIcFT|6c4qE9w89KHu%>zfbY>^j~GNk81`IalqY3UYruGBcbDYNajQ_&PFn4K|t6Y zb5rmcV{y#mgn+0OyrEU}U5EonUBTjHG*BKcxwlGSg7J37_OgQ7UPENkfRzBJ zD&amz_opq8lAnM6Yk@{Bq3Arl>?l7l7|X7vL$O1|3C%sezjetl*d)V{k-aY6`w8*G z8yfhY>Uz;WtV$T)UL2jj>b!(S1_F0OH3N|`0oJCiC;p+SC+iX(rHg2vHu9d7_NGPY zml=q+RwuRW!_BUCeM7bed|Tvx>6RmMRjKrDIYo;E)3hu{9lEw(SZ%0r?nNFc^z$&?zo;U!{mzxjtJv#V~0U0;L6hXfyoE)FBZA zNkR^N@?@R<^rWpp3rFX4J93Kkn(4_JWhw1C0o%}a1?Sn@{QhyCPW(TFwYk!~-pV3? z7XE+l<<4$_|KHnr-rN6vlBehYd(OD$jC;=bBXGuX8tQmp8|(ii+2Zpw)B*RiC~$+U zoH`@8z|Q)AIb}}tv!rJ8!RnBPwZ^I)$ScU$a!qU2B$CuI(>@k~)#d)zh96OIzdl;- z|J~<1&kOhe&eOgA{{JM8+)3+MjP^TZd-5yyL*o48Z2c7ePvL|3Ag^ z?%npE$&J4l19dm`116Rcd$o|$;?CKa{COL8{Pv#*N04tkvrtj7?O^ka2CCdpXIxUA zbL(q zFzLf}AJa4lmgKjT2AI*EMiIb08vMYpbto@nA94(daga&)A{XlyV-sN@S;_1`mMxu1 z!B5D>1S1uyI7SdbG-B@NXmC6wD#8yoqC%tU4A5xkBXR)-fuIK96|^-Io4M!@vnxZ- zrT%EbV^M@2hp(1d7f0CQq6_(JXSLK&xaF!o&IZ$(~__mGO8x{Nsg*CEc8 zbC0uhjyxAc!;GSsN3joQCGvGI5TOynOepT476Ie~SC}Sp`!->V8x|k%MI_Ic^X04E z(X-*s7Wv!m=;d%nj+aJs;s?I)Su&DGQ?+7-otr4)usgv*NH4X0vpZpe?iO}Qb*C>? zr@Jri)oYeTcXa(12#3|R^Z$L+KAU&%Dr=l2OQ~I8t4O!i{oC&7i+c@~Mv-35y{1EN zNbv@so z^oXh`)2-{0Pk!7l&x4oekO>wq!Bw?(fUbMa1C}3Qfo^dU*LBE7J*b=apjOUe9IoM@a`QNF?2rJ5kOB4 zxqHoGa=f53M$XyHPei;VYPlh9TP?noRnF2tWdkz+Z~uAlEejT8Z?wCs^V?$VvMkFq z>hd5%1H?Bof%#P*kUNbM&!0ky3W1UEw+53`5Y_k>us`-jyE~b!k?M{3Vv8i4+%gg} zx}9TrHPa!i|@u2JE5UEF&UeR`=Wpbbz9wF0I}ygBKSY(B515z+J>LH zzudO}8V+beZnCUImLB!;^#$lJVMB>HO(c))vRKq^eGw$zv1MWrP%!Vm4!05 z5B4RmSAyX^K~p$uEUNiohcTJHs#QvLtMzyb_24zazsFX_#Zz;}tmb6h?7jrus$dI~ z`~R9POI0wK=|Ln^jYx_^&Z8=wm5wwoJPf4#iqYKlWV+Ybs7;zlFp89(+W5X zKQ|%tt5w~fVhd)Pk&yM+9ljsQv-F-!UOe-dYt=J^Z&phVd_Rch^fX=I7_8!j8wnSt zI4d#dVr0@f=b_ZQR-4e++pF~LJrNvJMw2|m_MXg>z*Q@<;rXs0j+sNJDv+;(^mZdj z?cnM;WN(K!-?61b91SbtkjkArY?vjTt*c>{v>j31OquVW?QA{E&6b9@dB%Qkb|MJQu6cqQl*)oiZ(>R+^;;PbU0}{)U(i{h z#D%iy{P1C*hwq2J@M+-xla0yyAy|MZEV6-ur1jCI0%r6TO&HMFPJIw%XOoQJKJ_F! z{0!lto(E~qAJT>P)2{762MxG?7g;Evica3G=I&0&T7{SUfRz2*Ar!#9z|Jh#D&ftn zInW+&X!6Y7XNpSJ@)dpl-pAfNE%+bu7i!7!eQ$xb_)mLJOZI;+c67RoBKAVlnX#*uq0bbeu{cQAF-Ce%`r8h|C626gOvt?HVn zSBAjVf%l;LTzv~>bMdct=gAp|5UVxQjNmKesl|d7*MQAzHMM8u!&cXnl_0xYA4$aA zafKI(-HqBq2LVlzY(9!_^OC>z7Rmyhn#v+jqZ7FFWl?fn1ww{o&aHIaR#kX!TTu>C z*qqyM1!)DoD9X;VziU|>cps+$>o$NKY?v!$Y~YkfH#Z4IYyXOPf<@u$&Xt*(4i~w7#jzrjnToxMEH9OmG}>(b9~oE2*Z;-&NFF zsgk~ASm+fL;ca6rWp>n~sP7dj;@-0=D@e6c#c}GY=PTt51y<@lcfW%1ht_jt1d2Mt znU+i6`b{iZRyyW_yF3_^%L5ZdV*~s>+osi|65b>mbtOYpM(-XVj?}v*Aa$AY)X7KC zc*aDj4LIKi*if#r*)jE}ROjJ5K2xXEdNIz4!^jp{_oD`$x75@Sso|(b#|UK-7BJVO z^zVq|8d2BpeJl0Pr+S*`Kg+WFK?#eb8+ALC<6=<)&5iu2Pw!}3m(OcPmc^i3D*aV*; zXsz>9UEDe6LEwiod}r*DV*9O@;w% ze3vF;h*FrRITlp7^VinK?!2{Ix2&$OIG#&*i|oe=sEztlP*(LfNNiAFOxald;TIx z7nMDrsYv$@@}?zKUDRWUh-3fE4o59F?6jOWI7rquD z=PGCCEl$JzQ$Hdu{ z+-!3}Ka>UccAU)$_zocX+L^M>XouAR;*Av zENj9Pen3BO7F$Ty_K2|Dm~*NHa4pDhWq37;S;P}x@K_FbT>32N%-Z6nrL!~$Dy9|d zMCM~225ke9(~k^^qvGW`#rH#(mEM^EbRM|(3|T1RC8W_CC36)%4Wprv0ww(60~ub_ zxge0?ka}Lsl7x(lGK~GRVMFS`(bwxS4P>Dec0pHZ_B!`3Pf0i=&HUY>7=?% z^`USVRMsqxyL6m#Q05%b@<#9F*%2{ShV*UgfGeiLEVA&%DfL|kQr_~T$~VVS-|y~q z8J1N$sa~$ayQpmZnTAUS;K9j2cNTP`D+7JZacw_s zd;Yo_ChKHDCpe35<6y;ZULPNwUS1tn?#_x{0-uHAc-66BCm76hm5#b>y>e&!$0t{( z`zJMvS?-DYiz+jdF5R4+AANoN|68FWG~HRPtBZp-XGa&^IsxD3(lylF{`t}2@%hog z<<;Bs<8Hmg%=2UBinp=9>aIRHIXdmS2lxWKu&Xb`mYZ3)-iU=B(6KG^addfbXyWkJ z&of3v+;ZyrC>amc1BSc%fYsl%7G&!592}k3%v*^z-IkqPVJBCTCLcv?(Va%B1vck2 z+6CUL?~eX)Uzo41+44bfM^wza95(>^Z2$7xuBW-B^;M>p$IJ_XW9kg)!JG4=!_y0Z zxErXgy)-~v^IC`2WXmnGexveY=sv?6KjvZ8(X}bb>+)u@M*yuh9H}A(F!4*Pr0#Zb z(;T5)viS|U$1arEIIq#aFv8?YQ{SS9u7cYr2f?uFzo-OUY&VL*j(GW{hn)CxhF$q^4?gr zG@AbxB~&HzRWJZL^y4vkS1HV@8FCFJ-NO9ZZshrQrIFWkPytz^*rP*_otcg{hTAD^ zu%^kwI)$seeAIJgGrqdid~eA6$9bCUKW0?0Te=)R)DEOA{_nFF#r!Y3PxpHNuTS#S z*?*|D?~Oltqc{G@j6bY)6pk3_kEFsHwYLoEEkphVEki!KRY-51QEQ&j+cf-s zHVx$)PG16ev4u2o%95#0b`Yu|64K-kV+=t$NKk)&BZ*Z-L?6kl;xjOt_;<0U=xrGA z`MnJRdK-q`hM`xbc2G->+*tg9w5X$Nw(OOsz4EkIp7zSqKbZ3L)2b*xOD*l{%F};c zRc4d^@1b!Y_0t;v=jC20|7Rcn_tQLuQ|fmK{MaGWP+f?}WR=*C{)#~zt1^%E(Htx8 zTK}=aIQkzsexrZ?z>Q5+>oo{|=TMEMl7E;OjeZ}Tu@BDJ2WRY`Kef<*`2@fBrvlsZ zKRkQ>tQi0CSs(xV(>#UyFQ2X1;(?KbI<-Hykz@6lp8*N==2ph$DB|A5r?Q$+cejoO zSkM#=%OL>kR6lh^D|I*(HY$bf5d{49NJYQP>P>SBkYRbI^iwfo*;UvuX+sp~j)^1U zl+^^s_!qwMn(%+bJfK+CaLQ(<}q(;!lpcCLwE~^)#qVZVkhV1E0~hM^GQ)|w}POq&HQ?GgKD!x zt&{)gM%6{5k=z`Wjf&NXnjI1v8|fT8$s=^D7a>Yq0XT2US-23UotJ9$C z$4Y*k!O$@+x>+Mo3LNYw+lAcNIPMnL?fjN%@LtAXmS?Nb*kM3TGq-J2k1>;*D;ARe>6S?Tf`Z<&d(Vo5YH0pJ5dIn+gX0q zzmmIG>l@6SbF%*bHvd|WgYrO-|NKuP-;nh?hy2`VE>jI=84~B`COeXXQF6g1PFZT^ z&P!%S))^BOj8fm6*R@!wVy`e%tMfO?v>VKX-d)D*+ zpX4d=Qdwf=k0k)meS9(zK%v842q0$#@B3Z|(8S$kbMycqKy4#`bRj@Cum=bMtZn#F zgaBFlA5RF7_3<%;0QIe}Dg9K?WtMdP8!s6Aq|5pD0 z<+G>7_ok!$`@B?5X=eG+Ma)mbkIkTW7Vh+YoR z#Ng=}dVmz5rinkg3_uU-0TKYS{XPo+uUr3k+`sPOWAOfUZLiAtH@4H6@2_j6=lYu? z>y%ya%#B(sCij+(4^S6aryaN`D7B*R_zGvY3JHMj%E$rotyP3s(GQ9ORFuf=YG7pb z8hE-#{OH9gH8WwK)Rl!Pl@)tY%EuC=WYhgI1St~Bw1`ov0eDm)iiDyUq5MmWP%5y- zN&=Mn`j0C<(fvHC1;rox=`R0C6Tuflb>3I<18cMY*nRP$DF4}cvDeFgKE+cw&A7_y zCiqw~pZr$(6oQ}19((CeZnb+6P#u?Fnx=cof!f;nBMXB{W4otBsJtZ~T`W}U;^WAM zO5HrRfT*>DRis4S8tWt~YHg~Q7xh*mZDNf-I(J_>1dq?+=ZTsIM!yA?N8|SM0QEfo zzY5Ra$XAy(SI=Dkk)B5W|HgfA?AJfF^Z$E0&kFHhc3ljrqI2*Ib>ZKKsl1U;k;k|Lt6l59y=j z{@;E2Vy6`U{n@kr{{IxuyK-R0vi(WrU5%f+UCtHaj(@4^8Pf#6iRf=B8|c~43BaIM zK`JZ1ffbMj`aPMvc*a7va6MXyxN4h;RDt?t-gQXsR7l2@Ppwg?@SX^MA!0wAHFTH@ zAKf8kQz5-l69BT#DlqhyS?$w;|3Pl+qTJTyVIpGchpS!*ZT#Qv)8|F~@7}XM{_`h! zDj2)_Bp2Su2ln4TJ60D)GWwP##L;bzc*MeF?oY+Y=i4{CPKg(+E1BIoHgE^op1dcU z_?h1<5EuDdo`^#~M#B9pNN0XHCQ}+DEcbxorNkcc5kUx$4Zbx5SwI`13&Ls6FRgeC-D;Stf!lbS;$!Yu~G?_*?47 z%sY&El>0aslJAaRza9+9$##R0EyN|0GSsc^d8~@_wb^ z40+EXS{>ii-&F#U`d(v8IW=CwUi)EsXQ>E_4JK(61#H1WK?49G z$x1EeTBGFdi&L-J_8=|u8rjnQ9y<_SP za;YF{>bttMYQy|psjD?#HQR?7SdC0JuieOx=QXF^S2UoZ%VNg{xS}7Zfm_R}rGU~o zJRPT0FJGG2pmpmo$4p!KI+`-Nmr`@&G}~FRm<#UmU`#F#&de5&%s~AfIOQHB_mJyf zDiW%Xo11ylu~NGCVukX&$Vbq4#zbp2`gbPpZq4~&DzV7%nL0n#3*7oDaj;Le$hsd@ zr7SG90m=?pxK?sr%pA}W4>e+!N5=mh?tiL0aqdk9wA}wYPj`y>KX#t)^zr{b$y0q` z3a(ql+?&MCJyo&T55pO?@7^FII2CwVI5 ze+wpJ-!%vJL@j?&3|Lc4X(%L>P{0})xeJ;9c`|bOV!Mb-R6FcQ*8vbH?J+6>R8p=O;hv zia{55fZ}4%pcb#Mo4swdg4>d}7*yKU*5WR~;u||Efqv5kGn8wWAngW5;>I*$m;QqB z)SBMh>n;G*v~y3WVg@)0H`{8*x==Ome$)(hR*rs3g!T-<^m=4La3EctcALU!{I8I z{#nc?_mT$=mDs1h^Z&i>e0`t($|v2gnMm$3b(+#R=ul}?y_R_=qAHnM-CdMz*DjZT z`Jyjf^Y$fQzT#K9;5%OL55C-+Z}R)xLQ1aufXhg?U^d-e#X&_eq&lgIqypXiV&J6r&<29xA&rG|GT%d)8~KrG*6xX zkLM>M_9v-ep4RNHN1O)-JPn1d9+5Bc&KA0y6sswpA(M%Wm9W1zXoyXjs_4m07L(U=AS3!H%oklB&bQYcQ}qlKP^-mOSv_Ku6|mq_MvGG5<#%FBZ-~^P+>E81fNxG=)0WE&JcYx&=G^*{Q z!CAEwDywpiJ08!rz*xo3W1oX1wRXj|@2$e8g1>AgZ07p$Vw;+K4%74<5* zd?berORBR{(5Z*Sn7V&RsTNy&YZ1V9ZN^C6ALYAr!qi1SI-$Svm^i!6W?Ml&lm+*8 zoXraO4(2qRv5GK3HL%*{i*Q%DUw;k(m2(v02yj-#X}Z~cnMnCQ8LF^K1%3GawdY~S41Aw z9Jl)6gS>X96vEwUX4cO$v9bgUz2gCG5t8oEV|TI)^*mFhRB*y(erSqTHAnJv`*?fX zz8!zbU#-88bxA9{dq;kG_e;5p4uQ zl2|Mf#bJ}-kRpRc#`Sy+84g_@COlyEr4O*c|Ht05Hn)u<`JKO_$DX^yDT%U^+ua)1 zdaiV~W=1uR?eZhJl#{X`5|Xf{2p#~*qgn6$_N#6HBuG(yB)Pq*tLTF*kzk{{(dZYz zhJS*}A~$$TwUGO2sz>D8gq68A*P$xaV3i1HEam$>zHNMNYuh69^~V~|c!j}#!#J@^ zJ7k+}nuV#p5A!CQ9tre&Mcns6`lH_5-Egt(rdBP5$sW7hsED>s^V3QFgHqUjIedc~ zMRU{X6rL4EF;|>yGefp|W?uBdqG?^8FSnkIQhoK?WcpXW(n0<3TCLN)*H0nqq3Y$W z^~)cuS>D>^8Z|*irpd>)%#8n(_WB8#mPa6ajrF`9>RK##wo%-#S%~la^z`!P>SA!- z=#J{~{iNi4y^yB%2Zx;VN!w8+pLZ~e{m}A_FV^rF+GqWXi}$DJM}IKXeUXSSoVJ$G z!_$+WhCkm7PTt-8G&~-(k-BN_x>zyZG>spgflplGY{U8N{PgYM@|VHY#ZCX}@|T;- z4`-VYx8OblJt@+py(D83o8i&mbR~Lt;XM>BUb)pklG!O214F37xXm~z2yu7(OA3m)Y#Z#ep zVy@Jh+x3`KzkNG6d8!P^`JCr#!@qgrInVQ!uR8Gk(j8CfRaw3iO6F|6Q^?AkCVcoG=gZw2m);GW} zvppurckex#)p=(6XYYq9{nJ8KAu~B$T@;G5G$=+A(zv0?XHfi{O6iNbGxLN>;GbKg ze1l){NGR}((&QUDXz`^Viwd?{IlYOd4F{(^5CJ^xth9~Q9BaPCvMRLUlkzm!?)jDg zJ`@$FJyW4?hv}x^-L>zot<=DJ6z?E4liuIAZW?Gi9Ao2R-J>8LSP%$Lg<6w`aAlxg ztQ4-HvHhVk1C0{sNMmz5Ln*Iol|I)tNIWdBjq<1dV!a4ztuNa};rC0d!ENR!9vj*e8 zb(W{mHspLtdH`M)6g=_NdQ{)-#BiEtE08%oi6}wA6=WjkaTFh2+*}x`c(e_Naz2+i zygR&rR45(AQ(=1a*PYv#Rjlud=v6#s zx6!NE%!}yNe@EN!jwvA*8iq#$9mTHdRTPVqvz~3I-f9P~zq`&v!gI~57Gge) z^-Sg_`CJU+$NDmam1Xv0!3|!B+3dSpyiOcolaP36V_BIT2qhz{B z=y02e3)n=@NM05ijg6UcxJF^DkZ|W7?s!84 zbJ{+qVnXLkajFif1uEI7Yb80!gdLKqU%wR={P8?IjiWA{y@NtYq+aG+dD{jeCsar> zvK3-hAD0+jzDEhNX|2>@|JRKoYg269M2jDHqAm$SfZEdL9ALQ@@BKhSis7E3!5?#N z&{^k7kS|SCA9@8p4lqi8f5jhUWBGk=Np8Q9$n9zCn@{e;Fx5Gsx>9X{y?!s!Ed z6-+hOMDBj0Gk0_)bi36HleXV(S94RYu^ix+h32XgE?v;of@!_?`VDluQ<{Q?6D$bk zy~Xx&#{I4jNO_UTm6tkcN3Lby-I)4XUA1GHVnB%3wQHYVZr6VAfaWCjkO^%x0V|kd zb8fi8pUgnQav1YK#volND?Y>=+nMF|6`k(cVD<;OARr6P?Wh{*cAq->ysU^E7-Z;Z zIXxCbU+cvj;jQFC`OB!RkcTNS4Ws$0>#mPFFy)52rj)1-W}x3s(->fE@7h*y;75>I zdSkEC4#9GSa+H>Q$_eHK)jo{M%|9cgVAObYWL+nkx=xd0doYZMf;*NQ;s}y^BJVtF z2)9g8p|%ci36Wp^#dW znX+=}5Q!^kv$U*$vt(AubRZEUJB4rg>VSH=?v!6Yjew~sSrjB@6zYOe3}}D&Z){#< zyz`*K2*<+q)_g<4=5^U6~}6}xioR; z7qy$JVQT9qSlg{rL^=v|E6vGF(c#&n0bDlymTWOK7tn`K0 zl8KCsi8Y%PHHt178zae;iDBrcjACX+g2`dz>l2SUkP1yO+esU*Hk+!ZoVgtA0o9~l zXY!BmYO9}@rI9^H1WrPw4cNVzsSTkSoXM1YQLV)^=P44z;6$dJ;$fhpHt;rNGN0mq zk}CI4!^A#x0w9Gr!=%HKaI9?(zG}v>b zQ+$Rmxr(=5SwR}OLqIkh$rTMJH9-P<6PzezinDa($m^8aMVJlNa4&{KyQvlNYjzpE ze(<)%=uHfjpW-Ik(+52~p6uZ4-61VGw@)!wRNd}FjiO5lR%9TzH^83BO`|{xs1x~) zubFQ|^NI?&+o2V6SF>9;=0sHMjyPF)#x)O`ob{ez`d0T?ib&&J@^vzLDNiz{c-JA| zP7dF82ZgWXqD$LBcRCRYCNgN0hh6)LF(Msr)Pj#(a1r{kd-0#(U-93ct@4~Nv;mfB z31*>kl;k=+;}v#=tdEdxXK{~szN%iK3Np=T^9|p(%Hd9A8Ss1}Rl-B2zhYVT3JQsq z)(U*}HWo=lgnUa(<^cx{szRS9s<_r4xAyUemR6`gZtZ{E z3ds4m^^bpSSs{9lhN#X4$J-7V#U>MxSlY%MGBFp%>W#l4D=9YlVRv^o4D1TU6QMm1 zI&FhT75N9Qg6V-BJ)6wKo+6oNE1))(Z)7WxbKVP2!S3O(Z{;95iOh2hrj4{x9$Rx` z?|bx9p^J>IDi=vEOtCN^QG3v7^;%CBnV*?)L-)8vLkd7ROYyN8TUuLMnzp3U47NQ9 z!AdSF^Gzl3SHO@{ybinr^F=a?$P;5Z@CVf%G99&iXT8(FzNs*vIw+poH zdbf+JyPFBa70V9F7-=Hqn5i-Qy{$zVGnK9WYw=s)c8jJaEOoGD9y3jEx<>pONiFKL zeoE9qldM)RZ6Z;`um|S}qZ^H(ZDJ}BDLcQ17_CqLoyToM4dhjVvy1l&jW``rznjd#{=1Q+h3dl>og8bR%|@ynpJfeq&NEAwv(%==X#kd0K+y<3I9wiIgy!z@_@c6L z!OJ5ZU30f7#$sSps5++-E(m9dkrejHKJ$Z z){xTFzFI)a-9~KOppR9!@X4NWs#3TJns#)#Wz&$XU!E2-Nl6*G^sxOp5j9C;rl%>c$>zLOqjhOdg1!OYa z)jI3?%nT_P9PopJnOV=sUZKR286MyARr~$w`1UVSIoK3s-!SlHg;!r)mLoebj7>7!Fzoqai*YBhO{I4~PhUp#hl`K(O3H5g%3Z zJnDd$AU))=VgO4#8*^~?T!gudMT=| z8cG({F5fy5DqS(0cs`*pmwqp)2D!*m%P#Uqk&OBXe_fo{k-K!U0`kA^{RZ4Nv^dVm z;@XiV$+zrfp#=pupf-0ls!~Lbj`caR?{=-#vEGUzsiMd!!nKDES{|vio(r7qJ8v4; z#Ad>2QiZAgcCB4K7%FdXe?nLft8WM8diN8(e|lhl?{$LfE~3h1&TE$!YRWfdV?uAw zjae;YAExIQI9fJO*o~qtjC>~+rIFjP=;7GA6}253%Z|zH+H)sKgJe9Pni>2EyLFhB z9E9qosLTmA(s!TAyBvLn`$a+j?DS~>K*I5P`ht4Nq2!ZBHpx;OW$Kjj)6X5|^2sk7 zXAh*Snt%UNDvKvpc=7-|X-2J283jz|)LYWbCv0&Hk~_ z=0E7Er#!T0ANrXayEOdbD~ni(LA#8(q{TC3EC3o%^$CGzLQ`;EUH483%7i7FY)#4Y z2LJ{GQv+V@bSJY%i_ctpkJ>)|5LnOr8c>hiRI%Lfv?kRUEInVM@u@ErXM$k`CgEED z%2x;Fw@!Jpe8cOY{8rrz)tbGy*9z`+b=N{U=aYkaklk#7&-=Kqx)*f#m8IEs5nzu4 zSpGnGwCOx(S>lN;%OAdFNoV*-o#g5{J8|q9tBo~vkS^HJe;t^^gHqw>_=&uc#VF0Z z?Z8V?*+mGt((mmv+NS(nsH14n{wf!(>+8>5wzWBXbWsDc zVQyr3R8em|NM&qo0POv1ciXm>I1ZoJ`V<&BGl^S^k{ml}G$);LoutjLxp>)0`^@U; zvLOaS7$zy3 zO(91YaM6UHRcxc!?RtCOH=O`6pW;Lzca#AFm;neP5{ssAJOymQIEny_1Hf^F07Dcb z1_H?87&7E_W<=m!MM+@L!M*@*K2mt0(MuVkK8ZPsdHs^Hx)MRg5$!|95Vwn*hGa^5 z{&;_XJl^*o?>+I7c-lEfiwi;nxrXwtbb8k4b_9`T3-ICh&xhXdnK$SS9(E##V-9hQ zs04r_h{F+>Vi?nOjLH8_#S4LTHfhUI#0t8TCGq*ID>J(DMIXj6T&zmUVw7E5XGG1 zPMHupOfpnC7|q`!;apce2~~d@bUPKsBnxX%e)*U5v-L^=R08% z^o8Bi_oJXF5(EN0ib4VdL`84as|Y5?@}=nG)|Nm-52XI8>j*SchU7Q(hRF(28i}7D zX(-1+fkE*c2oq2Ifhmb26mu|#6bmYleNG^g|MM9VjVVLT1BWmMV==)TKqg06u@;a( zOxcctP7w^igpvp-+F-!On0b=k=8~ku)Z}D!^8W41Uk;A-TL2{J5nv=j^3PK7ql1%^ zU*8|UD3*UkNi@n{0gc1YXd!0LdT$6MTBs6*Pv>Ycl4BqY+`<Q3pHvkl!vH_@0mMgS@B-gkN zO1>Crubi#q{F%aq&|;RXgEEyXKpaj-U_S7kJe3YeHP*k*Pz;pb12B{HCKqB%;vR|; zzEE?$qi|ws7{>?*j)Vd*UYN>{D4IeVgorUfCZaJ_bKBEx8Rbgz2_Qje#t2?d-VuI8 z5koQ8LzWNF5utnpp6x$=oQ>u|p!`rUY#LZdr>R(RtT54+oWcV-)$FMSz&Q!i2!Q}o z1t47I}fGV&E%4jU%xr7C1Bi4jo(CEi5Ym6xbukXCu%@ z+*fW9<^@&}mX1|{Y<9(4->~~;_=1hSkHbZ2?HS=IClp;u!W<(2g8&6emeozX5>w`v zfpe(96sElbgCgY}0u=q3BF2@33UP$9fz74UsBigR#JUWK&-y7tv^Pz0fciq*rX2MQ zRMxjACwr?<%4O`Lxe1CP3WZN7H5K@TiQ3Zj&8FhyL-B@*kJx*t6lL^uWs z#d92@sS>6dXFkIWP|=nU(+iUBfH9g7iU3XHSoA|elEoO}T)ffz-&5#|=Y#kh!E?kc z>ID~so`aqQEGC+Pix~j{k)mY==g8o9kfFzRz&PdL0>-=mi4&lhHv&WsE2e;4#MYqh z030(egglu5U%|b=0rISY{y&m}w;xYF02neNr_q`etyhp3F*%|VLFoHP=^TPA1X{+a ztQdMudU-pduP-k#6GLSY(mU;*jS!h={KT+pLeyl1xsn=WjwrDSZw^h)$8+qG&!q0v~sd!W}#^*--&Ev$ySPrZRR@P?yj zgJ*+MVNs?BgMcE&o)2WX!RWiecf&$C&-1L`38B2yYDsG=0Wbo~>VDyCLJ2262}j_= z;gR*FC@NhetEGBIbstcagpeaiKTIQ*YY{0E3m^tu7nAc_f!aV$MffA|jaUFZKV_Um zy=h3sFl<2vIsJi8J*&T0R{+O;m<9-RvogJGY5PMr zkaGq?e6A#>!AjF$_v#9Gs&|G>JG<`X1`4>o9<{9{420|Ju5MG4Ri}1%_3Sn)M#hkm zV!<+^noTHs=T51lcp}BQWY$?&1DIeGYI8e}@)7(<&{8L}u(SqzccxXzuxc`^YQ`^$ zU>s!PRQ)tY$JSPrISYY6q)Ht^#@<=#Xyt?D3sGYDL^+q<;P{*YA;^8mMdxRt1qyIH zAv97f6^?}{(%vWr*o>rMAVvtse;}VDg)+dg5FjLYSmR~c0TJZ>OmJ=~ITkU8mtr7a z5dR!eAVY7o-uLUl@w?aWejLe?YQn_?$1F|cK1ZN*COuQ^94Cq5BR_w~ROiJU&xdl; zBvYSIrRTY+v$X|2%#idqB@|8YC4d}U%&ig?>Dr6Oy5yp*8G=>Zn+pz5A3cqlYOG&q83G(q|p^SwH#wiTLg<_ggd6F~<0fiUZ ztt6pEFvb(axMyZqR7NmXYPg(A981D+I|gm1o~`X8Z|VWYA;-S6rnVk4v;M`)x9?AX zJbwA|!|QiHW~PmH_jP|lVMtV4VhSyQ-AbDhm!>we;IfmK$L|i_zBH{Be1%(XSK(Hz zr*Ny_C=?c#o@wLQlmg_9(4@%(OhPzShLy233|S_S!U%Ch*^cOtl66AuQERg|L6D~u zJ%>}lD$Pg6358SS$+sRwTqt}b_9A%M<2XXk9}l7qFpaztt>@;4LQZ;M0-!IR5V zwHo3H@)v$+ynpsLse1Qdf@QBH)lw2N%X|}aCQD()Ddc#Lz)v4O9D#$Q*LpQ&>wc2P zzFGyCF91vZ8GxBTBS0aNHn0^CmOL2thhS;DTa~dT=HT zr;tp+qhAl+Y`ZZifj06K@|lwB%9oTJ$povP5O6hm_uj=(UoA%-NL zf;8qh6p~B<)Q&nyybO@k1y6h`wz zWC~@L@qU3wO24)!iU>!ik|y(ACGqZ8@k%>xZ=*@7=GAKP7(rVH++2%-6irj&Z*9?u z1xHeTN+zd-28hZzJH5bhKrTjLz&4Htx|AjcHQGhW1A;k*Rq0^rn01vYcm|f$ znFwBRFjU4!LF!ADE>>Q?u18n>Xz_-6YtPOK}2x*Jz(+o=v}70qRnq2e>Fsn9VPOnL=%HT6}QCq{eM zHJRGZ7^B>XP=OSIv7j>Vsb@w_)vO&d)k05b8mxAfH5!crrpmm_HUfK2$j&j#BRVpb zTls1;mp*3T>dHG-9&JxFa(&&qx+*z|vN<#Qvnr?>ks~emo3oTM$JJJoadEMJ?)_3z zFTFJi94Fx2`wzfG=K*N-!Z;dpZ-}GCq6teO2;mroiiKAxCkOs0|5IAtGZF@-*efyC z+m-e5MnX1x5i66|j(!kxvEyjfSjWa!p^42I@{nyVh>3nhDEK*B-r)P^VCW6MlXk22 zz~m+|G8px=9v>nqC8y;+Q3Y&Ik!a-vp-h$+_fW?nnf7-FyMz8P)`Pyl?4g+Ihiy>H8ImH*N<=`Bs`RF|vM9`usQp13mf7@8 z2<3Xm(m3Zd?)sPI`CpE#5G*QgO>#{W^#ioAQqawmqN8@MOnuBiearGRR)OksCh zDq}z*h+};o#eC0S+JZ*;R24rpOGNzqut+3tle>yy;h%q(Etag#2YL45NV>kVu}~~S zeAZiEGJ0GYAc!uMKdYdu=Eiucw7v^rzFiP{p6zAlbSHgaLFP8~@>v&rzbHp^zrM*v zVGR9q6bHqyE(u7j4#vVB)s#{#qb5FOsF$PDf&@OiIVtwniU3(na@r)o!!iMs#hi{u z(UO9P%<7RMph7@e9R+nXuCh-rav$Ic7t&?A0DO@kX*8?f3aUDmbBt71jCDg8KL{~o zLahixa>2}=BZ|2^r^bmA>k>aOK8ZDrsP*CBI)DH zhDN+Tk#9wx^BpwN*hFYld|`^3-=$)lBqSu$1z?GcHT6j>s5qZYiY8vG$;?Tkhvrz} zX`uHM^qvL1FK1SLK9OG!^N*JObVPy|n9)>9jUUoLMhmHko*ct=$gR%fC#anFV}CnCuDCA!gv{7eFWo7BP&luk=2Ki3#moDF!nik)9fnVi2Z6?XAGfL|pg4 zF@k|~xxSBmw0d`|T6b7gxZ4k2J4<6_xY=M!3bEta!7=(X#T2pH&4D7Z=CrD9gdK47 z%VDOr8QRR$HywciVA;i&o6eryv@`$)?Nhr>2c}PU<)qykoS3%Vj zzenJ^AMxs-PLv}VNDI_8klNF1S*ZWCUwvO<6r6x=uj{JT?e6GZLA|5Ls_QG;eUtFg z*qD5etR4PsAQe8@RGfz44v0ymIq4$+IO~xx5%>=9XM`{lh>>m0=BwKS;L$YM71rg! zuP34xFb*IMKyDKN@Z--fcf@D#U*LU$;u8-2^XRF_rM<&rF0 ztcY3W7|~j|u_>h7(_Wa|$kbuKBcT(C71}Kne|c%Pp6xn4aHVV=D>P%rGId_ZJIbyR zYBLDX9Q+Qh8wU8DB?fLZtTIx{jxSbvl5)*qACMTisMPka8Z~KDzj{p(BTNJw8U z4El|DX$J=7^0R!h#A%>0%L+JfdigMnJQ%^hNDMES^dnIc7E_O?rq#r++-lKlR~(Lm z5K;bCB^#(XQ&>%%^yIw=9vl}43io3 zip@yh6AF$fnP7FQf{nn#Zyv4&MN1?L$_P9RWYDGgmP0gyn}Za_Z5xO*jX91`4$?9_ z!AWk$2nwf2n!ktYxHLk6S%p(c$`tS!jKL5*Vn{HzkX&r%VG(&%VYqAGP8y976=IWw z0hnPXWgI7fA`Y=U79a-`P!fh?=$~ghT2feB8!hVSOCBgZ9ONx4_WF~8t{K^Iu$+PRlKJGq9_n{-XcF*X>mvymLVgqwQt)#_-z}` z3>74esb*|RH_Ixa2+BJrIWN#>m!`)@huOx9g^{8yn2Nc^%qMaKK_6~|F=4$wQ$%rg z1XzGEm^r;x6?&CTzI*;to(A{dMm4xx$`mKNEM1^)?|MxA5%4?azuOCh}uNx z8cutb9iv)2VOCW|s0m2Y=V4Wxk_sm>;W%Qf;37+{r$X^sjDqs%S>8wz=!ih3L=-JRVdE z93hIOl0xEujlh1Tzy}j@xL5h~3gR%O=);U6HX~s$0*@=L%j4o< zG{F~D8@XNPC+P_64+i-LbA%#!0BL`?`&P$DXK1#sCw$r)6#af~agzT381uCsOq>rkaCKy7N22tft9vBR4Tgllfnvaszs zey|17B!C=g*Ob83xIV1oq#;)M^?=^t%|2;6pt7Z}ECCa2`{G?G%Al+)JC3<2`#Xr4 zipR3nyWB08rL4u<6AFO$*xSq8K9Kq|R45U%p~vRFrx#~W2NLL=cB^2t@Pe3m3Wmqh z$po7iAr&N31gnCV0>bSm2D72P+wBw!q9A62EOMba&xXSp!AdDLWRoeViXis)Ig zlh?vd(FF1OsNexLC!1a$0evU@@A_NW=|Y^8 zgUjUm*GE>;8ll(z07Dr2S?|iPRf_XtnG!6E;&9nSOgTUgvT%iW#*L`rX%5XG=FwIg zG@xwr$!R>*nojkG)9VRHr4Jw4<~Mj`{V=<2&!qvglW{S*3!E80d7pd|$AXv3Q%RY> zRa@EeOn^)SBBMEsm4?xUfuwho1IVE=WGpk}SOIAq1l`aG3|vWz1Bok1#wDLVDZgakktn@VC#DfE$6Uo%@H z|Nf)+TvEFq4;<>M+)%NMVOa${GFv$2{JEMsB+noZ_dx&`%yLKR+9S{%?guI@U{mn1 zi6E1i>(UU(6KV`RQY*j`emYV~9&}1bD0{Zu5mjW2a+cMvMPnwLpbl@=M?IyVg*m*` zMeel55g+N}#_{x2p5W>muXpywTSTE|xLaTr8xP1`eI2E$uVHwjjil8u2 z9-09GH4O`}548vM<--W%)7l;a*TXJgIl3GDnS>>uJp>LyG?f8(M-n>tnPa87^%_J zLUcMYD^iiPee~1~wpz|P^0JFRAi-4l1<_na*m+jBjao%fMCK?kN6<0LcCpo#CCHBZ zR#422XgZP7aohU%r?x-x&i+Bhr&ul?MSXhJekopu#D}3U3`fQ$RHEj< z98xWv4VEleWg@eZ)vvR7tSi+WGQ01(2#JBt z0%wkqWbn>pkq9PdfFk*~)S6@nqL&Z(i9H~4Jo0WgV~H*kBF5G#FOWoWkPsa6?8v<) zQA>Naw#AeZCR1yexjM@roT;RAVzlKK-47EmEsqkMBb03g>Z}Z=eaIN`v9v?9hU!8l zLn1YyAsINcxCH6za~x-q@15xr213!-5#2bRhR7NVYlDZG+yMgS1qwqkOg|me6!sje z?xA&J2PRmL_QOuLtn%-tEO0MHwYQ#)PRH7$FuQB!)VUZ3B2QF`&Uz|in{uJ;fIm{k zO-PiQHL-?ddSbwd1yv5!nV0hk?Ld}5?dIiBet2^N{tJxNd09@*kxmJq3DcaZFRJS@ zql3>=80v*>LCxvFy~y)JA{lci7&T(uTHuNt$$0wm;{TNoonYRAm1l6Jkw{~qBU%ux zyn!lBL7{1@dcZ1weavp~Zm@4>YY5Kq^-tI6RQ4kmHP`E$BdKloP|b z<@UA*-VrV}ydr|NwZ#0z(m0q>c}$CPwE--BLc*Y%P{=qAo1%S)DxBzMFlSqItadM$BJ5}FpVAkI_R(&~T=Quf)02h|; zN<3;^pUQQ;7EQ>SHq-bSp(IaH*}&F|d_WYI3`nwb#RDv;QslxQ$kJ|zI#RA%%v$-9 zEJ+B1K+dvhs>lbM@yiEQOE)+7Ry!!C#s~J>>1l*GJhhf(cBpFyR6pv*WFgmggZZf# z6)wK3 zI_=oK;kU+ybMah<2q1wL5vPy5Z?_$mxcVyU>fMpk(-P<;guV>@8rcWyO(HWk_eXns zk5?5<0gEeAa|-A11gzAIjsMtp8xxS9yTVEnlrdBkxwWh%z3X6W-LXl|K#!Gdr@ER#~q3Kz7gU!VZW8%%v@wU0G!CXepSj%8B@f} zd0QM$5mWB+9L#ST&T-~b|NYVCk}RqYoR$b?+onxtiIG*JL{b4IiS;#~3v1ReIW;Lw zKIyp9Qx#}+IxbBxCZ`llF^NApz7Xbm3#mVIy*E3Zr+ksLY4!^&X2OyKX5ndB+LL)sJ$=Jsk z%J!PFQIRl(XbY5%AkQz#o8yo*L^{2$l+~hnwTA-!3q*u*nHya69I4dmXSu3$W@2j! zyLJuu3bs+jHql9WW5U6i;SpzcZV1epfD+29K!uo6J76ks(^98Q$d0|0l!Y6KQOX7b zCJ7}8#gLvy0|yEC}|&-SDL z8IArGe7pTW|NTh(SFcq)7>*w7Za_J0F=c|ERkXnZGN-!KgW(1-s?nHN*j-t7qKY~9 z+BaI-uDXEy~1^`aIM*GOW`Vr1j|>Qq--{Y99_W0hV6LM@jGMYGBv?!Y~1R7 zu9@mfON;7?xh-Za^H~^m%xHW+gwQ`9lS}YY=Xv@Xyp3|ajH0QsQLLb6F3~YsdWCEW zpl&G{=M*h@P=QV&5~PB*eiP#CbocV|_rED&w$Lv!+4Fm&h*FGPy)8goV{2%v$$SZB zc9TuW8K?gjEMsn4j<{t-8&;%_l!MiHOhSUC{H1bwWTU$o0`Dg7vXjBP-^sWGb69&} z^i5#qY7%p|us-NwB`dSDrHVOwHXQ7pEjy+n)0u2mxkyN`G5#%dtbmlZ22;bir37Z&G#KYH}rLGKmpO$NPZzh4b^_O3^x z|JlA8?mWFNe`v4>AO7}W_xI6*;lq{f;V*Sm9{nczEll)(d7mC^Z(lX`|H1C4U<|kI z`+Dlk5~{vq172YB^%?Kdgua3GY*J{h)?zY)%pLG_gk%o!Pcr!%3Q+Lr6~rM5J{=OF z<8c(U;q9Mkx5g-<)mwFn9Qx-J&k@a& zB`dHhjf@NpJve$TbQE>jm)4)OR+3NGm#q)Gws)2(#rL}G7p2R7QM)F0tO?)EUdg={ zdas3MQ0}$RdoA={3tg$jznK=grH|F;sqjje6rt_0R~YDvOu=PajRmh*0P6b7DHu^jRMI z!2i8dJTBsWyZx`=Tm7;)yn)~8=(MN#^)q=cQJD-@3>le#cXqKnGq`8cG?p)|koT+m z)|JHqNcJPhA#^69+&l9{C51JYM@KNm6Pe#UyW~R#7sdcm3KweWzBPriD}a08lkP7} z@Gt5d+6c^t`sF!}gHfKuF<-hcfE=1-qK7o2yK)}2o@`uywkzsesC)8VcFAsMOSfoF z=aC6u>DZq^L0#s`G#rCN@kZU^-`UzK5S^&dlQr^_eqy3j5;>WxQsvBJ$a(tBLSCma z=ZpRfg^>cvOs;Jl_w|b+$!~S+-Amp+(8#p7jMP7^U<(s z6fhBEQunPgGQ~cuMxAsFeMNBzOI}v4=c&!WEvraJ))B>X9HJ?D$$Y3%&E~9f3ncC! zpU*nfcDPM(psg>JVYabfZIHLX`@|@WI=ZHK#hHG4Q1;_F0%eN9LjSmbHuG4AVigH` z(gg#(KNyKO)W%>i7(ChAlm8C}gVO)|Pj`pA{}}G=4|aD4gC~RCe+-8EgC|e^0R|gt zzwt>Khx8wV8@E;N+*k5k$@SKKpk}>00$qK>jF|tPrUgQ$%F_+Ex+*}V%NGd}rPbXV z=50brf+)v`bw}VTvn#s}UAR!d1v2}1y;B0D^^v6zc8XtXkjuVn_T~Io06!)qL@@4F zN?q?%p;~Ita>!1^+jbx;Clsy55`{LEbzbdE)+y(g>RV^$D_Y)Nje*tz+Yf`xdV*-f zIJhyAKypp=E*sPuar-7~3Ac5VLNniF7u(WS$NkG@4N9GgU~-H?0;|jT#@u1PoOY!N z+mbJ2g7>bLM4e9Cem={bxkW!S7|Xb8rLh|kt2EaSs6))uws)h>iRKOs0lp<^%vWf+ zhH6?jtgf|wf$5#Md51m45uC0d)f$&fR=VZV$;t}Xe?3xwTY5r7;RELYZBSUBfCK^cmr*aO2Pb*hRY>hU9a0H z0!0sUy&h0vMimf+{iAv?WmIiAMMwC0FeKwUO2hC(<(^xsLsci@7ic}TnA^hE#@E>e z%O?7&yHVq%-E?nO)>=2LHP`uiH>t?pfT!M$(}Iqne^&#X``{LkQ%YB9Y|TE8jNl6S7&G6&ACHC)e?s&3HObkCu_-n2EDwIXBN?c0Ka zS8&6OAwwUJ-`qeWbpX{Vt&d44mB05oj(Wq5kSKz23x=`6X!s_8!l3-F$yQktZPCOL zwF8F zCvR|sS8$8o<_?Nd3-lesZ*Y9xf*4lP5H{`CMwci`LfD2)uR>kR9lGm-%^bIHWPI3d zwy?xuy54|u3ME5o3$|v3V2+|Ci=z$2x1?)<^-qLxAqj44R9?rJ9~!xE3-Fak=C-hJ z`oiir`5g;;WQ(RAA}gP4bzc; ztD}=QEbW?ikv2@AEq9AHLLMvZ3Tq4n<1M%rt$%X23*kK-vt?9SXMu!R*+*YsKg z{TcS@m#AHp7H_;$FLaK(*|ip&>~300nk?!{LpNeIkk39SICrt|SD}n$<9CB~AL2QR z5o1S`jL|D~LCS|2MQldG7EYMjP^hJC2_+W8@C6DTPUj7_bA%|CTT>(sTClhE8;e^TCa)59sISt95D&d2?~+VNqbL6E67>=DOVc*oe3^E$!|%pO7uaKeVP?s zZl_k@wh8xGFPP(RFP|~vIG!Gc5Jz{z@WVUQ1|nfxH~OM0&E3d`+iYNVQ%f9jG)cn~ z#9Qq^Y_{=qyx}puf!t0=aBH6Qgaj`zqp3XP^g|j<(Vc}Q+$|f3;N`~{&LIv(tL+=z zTmZOx^l!sqyF5Op4*VBs>fL03DVpHRjqi>o6cf2Y7edB1zriR&4Sqr}*tE-60zQ!m ziZ+WeMKHj3Wm^;*yG7%o*wAfS7sZxt*}Nz=c02Y3Me+=Aec)&X{A$r%C?XskBgY|` z3g8vihXVU6#cd*AR)Q<@b{q;_ZQ}#IEI@iK5kir$x0-?k*W?p(%rJ?6!i*ETXx^n> zZ$Q}nX}#NO@ZadPUVR|q*R4x6jJFQ?dVO>^Cf1#M>1Dd3*GG5b%bp$|9j-iWOT0#_ z9`$NF!WDFuj1F;CDwC{-K85RBrx~^hA{T;ZUx$Ho=7F) z7{=4potL$Ec4Dn;71m1H6OC$cuI0E17p5aZD5gZ^N z(p5skR#E>o_zyA=lA~W_UY!j$!6D^oa@2UN;;VK!PTIObD$U-A&n2z=6q{{R$8_C7 zqc>S2`3}P0R%yRmik=}DgiWDCmF8|ohOIKpbtr8_jI~np>o8ajAFMGrS-n*SeFe?d z0^d9>Ml5S&9kZZt!x4>eEQRuq6#BQiUB*d>Xxngz74~U3Nk~Yhi<5*R7#xyVkPP1R;T6t; z@n&tQxgbX$h6gDpOf2O)J8O6g`e$1@@3?)01va{%V*d;pK_PFZpKLXukhp_+)>7 zP`&?Q@BaRWyLdj#5ID;3Ia7=l_%N1N_sK|FeFd|;zW3@1c)#d-LS#`-bAHcqmV)bR zbNg?8SM!T^C-S~6Frg%p_o-od8!`ntbZXSOx&np<3+9$?X_AJaWM04n*Vmpv_@nP5zknyC9xjxD5X)7G`uCF_<1ac(hOkI17401c_dN(yXACKQW zV%q>xByjEl3gaoNgDy`1gX`;#zE?w_Eq;oMC=RkeLne^#{HQN^uFlEF<2PWu05pwb zF`j&e7y>#8n7qp_yMT_(P#7xI@WfnY#6b5Q36wLtZVm<07f1l0vU!4c?_az;J$irq z;rW9{=jj;vJOoq3L9drz0#`Q=phv(TjKm4#v*+C_k0r?W1XT9h;CC<9xLkM5B|r?U zH2>QrEMFg$a1<@DwJ??1wLnssMJe<2g@s7I(?71e5&?fkfS%X~8jaC|Y}E3ZEzH9k zD|D#ETnKJF0PVIP^%>6%-$`yVA zxj$26BRdMn#HO%^_Gq%X-*PN3R-4C%oqQrsULODQ`p_|#ty^EI>B77}yfV{mM|PQI zEM^`j*AFX`o})Z!%NuK`;xLOK1+CZT(fb#t?+)I+EDe{u&%WJkg`f+Q#LMrE^)Ts+ ziq8eljQubTiX)j1-%honx&8Bbxj}G!-K||_=9)$cTP-Zpn^v6=L(!WMdI4zw9+)u! z&j!y1Hk<7jj#wJq@(JloAe311)}|i~5diS(!STD-?|vMCqx`lYfEfrdlc@a1fBc7B zwetG4kT9eqrx-MFF+(u`pad~{#PU8-5+osk0BZIcP~bJ000oGf>~&f4>DejDXglMl zJs~B1rEouG8?B`P*Q~4L7L=AkLmN|F3Q$*9U<$+4tj0K7T2q-hC^frrJ;ph!U#K?I zKp1~Joo30r{B1OwhB>_({iXqd+tYHKv%9vg({2slmBv%RMi1tRx!bvb>(`+PJ+2qK zy;=cd-Cg+iQ;MWedaLKKwQ6KT++?qtXXg=)19WL7sp0aCJf>j#&Sr-Mz$8on`ZD<$ zWMGU?oXwTszB+d{U|39K!|Y%V0+dkXi-`bm!Vv}M-!U-scK5&aK*~@XmDPT~!uZMS z0BRswL}6cBu`M;jawDtaxEv|brk%@VdkI7w10Sko0T`DzHzO1P%$xQ8yPKm$?jDWUnnZv4}n@BZtebc~ULECLvqS_2ZCS+>y+g{IvXg$&< z!=*ju%S{XWmTi z^TH6^y+t&v(Yb9MLBi*E1}peP9iC+dFN1>Mu``dv~dx}@Ha zW7do{i6Rnvkx&?Po9@xs3zzjNz5Dx(*lK&$7X#w&$R=gsi0;gl*=sWjTgnS&^I@^Di*lT_p%J&kJtGXsnssabFt+`j6?|h zoOMFG6EF$`CTwRA;s|pT2%~_{FawV?sKv@G2;*4622Vo=3>uyrShEB6I*wMMq&5^pzSe6_r9 z?WS7s!ygfs6lByfiXt)>gpp9x(|K@h>${m$&T~i#5dex72Tb4?hnQo;JgeDEV}C@^ z6kWc;aR9pW-v+&BzkmC$e|e=py2}A=*L3$7@sui$GfWan5{l*GO0dB|Fqixi*yiq9 z<#GrmLq&RUSbsir%}jfYAc{!L&@zOwVz-P>tZs)(4G?(dgtFM z%fW%f1s;Z`ZMkxMEu?TDSik)gM+cX?aH8(p#Ce*GvNajmg1b6rQ=V0g!pLv(kzgyL?3XIm<-qwhSr`3v5#f*P@ z4VbYYH5mC+?fv25B|t5%HZxy2JRe*iPUxZ1<1ok`42WRXG}e_A8%nsRS@ zEmmcr#?nOY{h1fpMGOt7nmVsbRczD=1&^HQ=lvAObDAPSa*|xv?yj1gI?Da7HRV<% z^|Dbj_usV`ts`?)-rsMO3MNI_C#?6g095CJg#jS!1+(jEPMH~x%~JJ0KUyrwR38Vf zGX$)I$eFRJ@XS|yrV~25Uo{}Nm$eYvPR}BD$=d4bKe{Uct7SSb)L_Zzi=xv|ZGUCv zBc4d_zO+&&g!=i-5`r2X8)t%fq2_7HnsF-HRgjrOsn~w667Xhq%;4{8mWj7P>I+)| zf)Fzp6D!j6M(PEcxJrgn%FxT~_Oybl1K2{_ zVP5H$ZSkZcXdc88pFaqWL3gKebj+&z39ux@T$UhiZPG;tD}{avL_ zNp)T=wbVWyzcDn7<7pcohkb=gPL0;v3O#DVuu&O3uTDq)H5<7i*yljbyYku~@5_I7TpNmW^9V%=U$j?kw)N($u}ntA6H668kKC9!$8j*SH^4e3>TA>iI!IuY zsSgeMdYEn9egZL$0~B+xr?_onj6-FQOQI#yCXHNLU*(F(>~zdB_e;^O)<3G;HK|pb z9)Jm!S5)bq_dxENkT0^U%gpzL1m^ZG*$PIg;2hC$Rw!ktP+Mwf=Hp6-=WVUsfvlupO-Y@$=M4O2MNJ3&)~ct=awD zEb9Kb#b=rPALBR>BzzM!V2S)64)=D8@_$$81o!g)E}pgIe{NB4e$I6|yyMPR}@EvT<*W7&KlIsl$*E9ixfB%m}U#sNKx!08rJ% z)*6y~@$r>BZvG!_!tBeQCH()%<3Wl45BHu92lxE{E*{PQ-CSHL%Wq*~f|%S4d_FV` z+LXTqzO3pNLZEdebjc|^GgRWXZ>o8?AFfa_sMUTZeNf7T@ zS!YL}JAolXUF~=zgiD8F=8M$+#&fjT0T1S4G;D{XFkEHYe&cAACBgOe$Q}<}?-S5t zvg?Q$Z*IrBAfVdPCu2CHKh?`}0(~-;iziWY;PBTdsFE`$adJK+M{6aC8QzxS*|UtNDhM7SIe>8^ zWNs`Q(n`z8an9b_Rw%I%7YR1h%C^R@uDi`ZnpjD%hFYX(IFXl2C;`C?8gi$lxujWK zE}VLW#r3EF%9XlqyHrL~q+K+p26H=#d%4N4$4H9GVyBiC%VvF*X;)NKz_gJM6iSw6 zeQ9yWdepRIoKyLspHmi?8!JXucJUq%M$ZT&B*@0p+=4?Umq49*xqis&gP=O|tao~)UHtDW*JDHBB#eEISCO+G3>#w4Ap zFPa;yRJ}6YLL^Djsg7_DD39W)*6YA;r>8>QJGEve(|CTjhFgYKTiChIYg@&k&!u*G zj!?2v;|U4W=Tn#@_6JN(&4I98&mMQ{TCI=dSpN6twxGW5$7%noa|Ijo0W{nHkN1X8D(C;744>ZH|9A0d zaU(R=a?u-lL>;FluV5RHWTwq%Z3M z*VmMUA&#dXlK^rw0&yCKS62?J5oD9EXT@@k)%7qTL513-*ep|gBh{CndV}M0 zx$pO9i*nNBJdt-uZG-MVV;p|p-L`x-l?o%FwIU0nqW$NpAYG-M0wp+2*y;}3&35DJ zXj{4xUdNap8r|4hzqs(efn&L#6s_Y*kJSs`f?l%-vmlsb6cQMCR>!S0zqu*UEW~oj z)vHasVSj08J+7Es?J>s@oFXs4Q^a@?hWt`oT}$Cmln{o6Fap=t|GP@f41;c|8%0Xq z2x6mEYH~PrVO2EyHBv`u7^;Z0712wwijKBQ32bUXWh88ZN-){lDyyNAh6rMA*Svd=O)#(ZrBs(z)K`vzB0 ze+qSKRUJ3vwbYvW1|8R?pnv!XpL<+W6yE;m9HaTN1{J*yrejdl2fj629n zdK{}uU)q6cP>>X-@g_KQ!Bbyd+^){KC9!o?sB}i=OLlk->Or^%wLR#{ZF#jEzhS^G z&{zx$BO&raJf@Ih#FSQTZAj=6r&)5&DqjXOfjCSl`Y@x2%}5w52lT2`qYROy@&=(5AfA+J6d{Iqj=scI zHbXGvv-@>+XX~s$2sc`01xPnsWd&F_SY!n_n^vdTL=Txk1h!udAhor{M1c20kRS}0O4C>eA=i&!#mP>&+%N15;2nA@#%@{%f zRWVQbrVNA;+5rz( zf_yM~4pstVFQnRih;tkVuNmkv8RgT}O*urZJaZ#IKCrt#aCF}xl&#TIK$d`5;ub2s zsQ10RQ)yiCn%2w0c3luNv(KQ9&%eTy@n12Y{iLdN-2JmER>ea0(Z;kM@odXO@drEb zsPx4pm@Bob0Y|OIEhYgv(SfKr2KL(qwV+UWO$5Xo9fTM%#EKKAUp6#vS6#F9AZAKJ z73TO*pFgVK8p*1)YS+c3SRHe!WlS|pX|)b>5+YiTRJC3RJA zI{a7crF!?SX0xDa;G7a{9ZR5DJDllyMYMB|h5249NjDs`ve9^zWu%wW%1Yg9X_~BU zmfQi_b>>7fv9zJ^{I^S^Q;e0!`%Yk3E@9p)NKW@ngHCsQl(?Zey8U5TYF?ENXg3wi z1*I#veyJ;^#?M&xdN>`l*L^R7F`gjC*-Z}uFUkKk z7(6YX|9!f*`{e%o?_E4sS0xE4=l-pkda>Ypy`D*#;(-TRVB9D7`+Gdj^*=)}2oYmF zHupD>1z=hJ_uXN6{Xco~^#1<;J9%pN%$J`1&$9mM)0j)o@!LQRg?X^?NPok5?j^8l zQ%P;+VE+m|46}rUtMw){N1kgtvPN!!o&kTOBm+9<>+7Qtct>KP<69Ay#txWqZqmsl zHBpMre(R&R#puO^5>9*)j=+b*BjX8!bc%Q>dUJ(|nI0b<|yYxQ6_ zcs3ZW)rFV@rMAqQ)zB`(ari;=lP$$O6HAs+ZBmI!PPNa?4*%^Qr~EfJ3~nd^&=UE- zH+)*o|GWS6$-Vx6Cy$%i7y__mzbN@Ye>h0+YkgZK7(I92S?ajXvI6I%=2p>Kp!L^~ zE^owK#<>)Deru-#sCBiSJka61yZwO{0j5;DJ0?M?wq(ZDLn9fwRw?VCTzN-O7Ah1j z50Lcd*SC|1&#pRVFdOR0B3WH&TZ)>jp_)~wG2N8&8dQ)an9>xoR4dKEtfz>&s^5X` z+3IvrVMNP$vF)7D*5RU?DwsFYE3fh#x=T4kO<&7b8Y`5_Z(WK}Glasp=#;t*n<@s_ zj4)nEM*cwOg^<^CzQK%xZr{qq(32Z*61@qgjH~i_2m|NV27_2&iZ=12u(*mzSFI0u zpyi-4*|{G-+wChb2*Fus*oF)8iotrn z1@L7*PW@kBUDi{#)!o<(SmytK`glxv9Bpe$k_Knz?5mO1WD7)+OiqPlE9#F92Gy{)dD8{j&W( z*uP)@ck;VVbg zqJIPKa@*;A?WlS)p?RV5l|Y}HSnr>WKW_fdk~`%8yL(T{_kZsX@AJRk&2!a!|EGCZ zf8(`fLf-J|>#|TNR>;|WvDyZh{AMz}*PRon4qdaW5!9aw*sJ)WGA)$iIS$bj1$jPX zdA;0``S6nYFof#PjY0_&*z;zT%!h|Rz5n&y!%pd7Ko3030$CrLH_q_P?5l|_%wDL7 z;OwV~D1VqEWP;aol^62ak-pugkGR>Ib)-!!mRB-{rdg zI{-|Wx}Nq<8#NPgObC}387>${kr~!2XEyQA&9XQ8xaI#w!oIR+nf!mUyH}C__xFF_ z&2zQ#{Vyv9@>^9LjVy_=uZ=l7a*3RRD$o+mHMjj5;naD+H>hmf_0AlWd3WBDpz-W% zbyV_dj$P3f)1wcQ$jvyb+9UL*yvJ;n>iXIV`<1?Rqg%RY&{3pKzK$ZQm5VwWkK|OK zDXmLib1w!^p;md_4mTIHZgKn1%Gjdxl|NI&azCTF0k+*x;J@+~+RSp{thnv-uodC9 zKpMi!RvP?#+b#dS%5Yb1Z21yzv~$XTN5IcyfEE zQ5>)a$_{Oy4%pGZy_dYP$WSq;My^#P#!ZTzqPaq@o?GKzWfhK^>o&C`O*FEmzsQ!6Q_-I7h9O@|+-RwD@8yIuV9m|gPs79=lK8~X#t~yIi zos710!uQulniQ-TEl9zxUOTo+CXvA42N=TG&pTXv*VOB3?X?;{kyh8S^lVm9u)h`q zP9OLE{r!prZ% z_b}-vBN{hSRkX2; z6n9;RtD$ifHQv3CD8Av-r2j)@u-L>TQ2WI<|uHshT`CJp2=h= znj?Bhq6AU|y5BzsLvQz~ciFYhQCrZ2W}HW9>#bL3oY=9sZBggImL0yBq4)&~5l4aU zD~%ytfUbJk1>IxBA&x6M!1j11b6*vIAD?ih{xl;h8aVM`xHAL?5#SIce3 zforp(<^nGH?NG=$Q+kfY)c%ASCv;JpR?t<>col-TFoshZZ&siEyfEC=D%x1kBdW*7 zQa);H>HuqW9i#KJ)Q)$C69{`$s zhzZ3+CK)dtomPI0(kJ&ux&uHZj>5rq{TT|<5Yb|rh1YItvQ*L4R)z5mz2W2Dpj!%e z{yArO-jA5;KS3cWE1m#C!J~3usg$o;g?@wMbG9wxTb7ptp8zK(^4f)M(EVqO!|%J> zd6U%&h5HJs=a5|$Onj-s>r`7hf^0;D_u#x zw2}7LlsAC-j5pbU%I)I7+yE@}6BP6UOrN{&>XK&;b&{J?okXK6^I`P+!`-Lez#DkO z(X+v`fxJ3a^txL`$9)H7?bXJG2A>Nl7V}9>v;WDghTGf{t$MC%m|Krc7N0`Hb(5;9 zexzZwvF7|R%d()~!j06crD0e0!3$iye6UO`E8NK4xIo;O>f0^fgy`Ed+QO~WMIdc% zv2Mpr8X9q;-*}r}Lpxu>4S&v4LhI7y8W+#4eOU$ga_(unDx`j_^mT2;rGACzax1AF zg*vq=h8yx)>O_5mqqIf^HTZaLGnG~K4mS>ZUS5T8%FJy+pp6mjtY{BQc`;SUtInc!swP?tqOnIw%27`h zd)|Db@OslijeE|8fj$)AaTbj8lJd4#2QoFF8gwJYJG}`GUGUV`Dx<6O%}QKde6iA% z1oD}q!CDaRDQyq9?H#fWLnVm8Cov--@@oZT4z1>NT^5W9=p+}$>A>-*g6MEjn*wY$Gp9mkks z7`{LuTr}K!Sfqp3R;4n)rT3~ALDgKKsQb9|V)Y`h52}0_Oq;voi-2A&xjdew@+{^G z5Kl@qiV#CQM_=M9n;{tT+5I}ZvvpP=gc~ih0;C(RvI49dEV2TeO{}p3z~&`Z8)QIv zn#lU@>+H*E9=jZo!7QAvQre&F}X~kFw#XfUibc+{xcEKY@Xnk$kk4J*X zJaS$S-4=uG?d_!=(e>$L{r4Jp^@^ zCx33yu-~rPKb5{UTTtc-Q zC+9d0UNgb8DUPRI-IPN?;7O=I+IbVyeGsLtyDt2-^2M@ZvREgGhU>X2-wT=V+%6#f zxp_u{nAzch;>ljQ09VR4&wIB2qL#fKepLG6(iJMTt6@j2$Sx)UIw_>h0s4Gjo{hYA zZ5Ar8iGY}ngAhZ8SaAyV%ZBFds%y3$#7qgE!W^qt4K3%@TD9w9Kdg@H)N-5}ZnIj4 zISCOh$E#W|8#--QU30i$;nbga%$tUGvr21^H75xP$#ij&2r+g@V#X=N1sPOZWbN?> zLUWFAEJy4|3jLxh!o@)vE97tsnN-12rC(2lQ-@YHUQrS$K2l`8S&G)8*o*70$IogQ zucZg1^FtKY_XT>UEM8DO#|nYSnDYG%}Sq14qHS!ccN62$F@N1 z29YZpjapeodO5AE)V-D#$J*A$9iUxjB{UOD8w$^VyVNzsSV^Le)R#+`_j$^za{VS9 z?)E5gLvwWd!?4t9YEF7kD3}Y%wQ@!loWb+_h}8$Pk8jd=6wk_|8|e_{Qo?GcX|KUlgCf)^S^(l$ITb!h;r=1 z@F3-c`7q3m9~RRnH(&Syb!8k+brRD4eAxd*gFYfb4!v^si?eQl8!jz9MjYG^TA zS*d3=9b(4isa>g~eFB`~s3~9h?XQ1}|9dkOMtB+%iZ@m<@`^N_n+M7f4P%q z3miet5sew(1gP1U*)hO4#i6`cCxQMsoFe9Rw!nuOX7Z*WnVFBxP#A(~NX9B91&*gX zfT9p`JV(-_Wxa=S(Afepno3g)JW|m?C{UL9e{6d|?o*Oj))Xj#1W^#;7v&Vl`|@9fe#aaCMf>t!^I|sbi+{{dHjn!`pfU8%(*#U# zh*;+vk6k34Z@e))?|kF&DCvCj|L<&pUm(RKW#ILTm#pI@l>C8w-tlmNU|*G^B_ef=##4y#DugpAMgv*8lF)-NF6(zl&!J9Ay;H>HL=e z`2Eq0^MsB1eft2L1<2!1;ZNg9B6YM~#JC`q|s6|YDd2XcMq)&1C?$cjKOn;n1| zfIwZf#bm-xoe=^=B*vUj0ZM)Lz-tbC5{Ag<3`B@i>@#@{fp!O`6a`xEVE}S4#d8z` z9CJkHFl0Lb&0&}dt47`_2GC_fRVGTd0|LyF5H6UyIt;Nih}wYN4HIuW{@&-587`(p)ktZ5R%`k+LvQ%(+fEM%c;K=3^jlfuN5sK#e&F$SJRzOV+1Z&s7t|5$0g4g=U=V=3hdrM} zQ7ShDtKH8(3SK@-$6`DLq@El~W6hb5 z5zEI9;201ZAS&gp2CoB$g$lgzz>(Z<1O5#8=U_on3eE)1XF{~fFJm+jVp`W$_*Tc? z0RQdy<(roWCofOm9lU+{yK^?IS+EDyEHS;Jw+g?v!5D?)LJX8O1-j9t9hA*EL<`WXq*{!OlCP5wESn&5mAISJAx!#<}7 z^&^PmzJ6hY+!k$!<=K-KRZp|54YgDSEZegH#UwyIbSd<+jrFe%e7Wr;WwR;d=mIXB zEnmR!9MK*pQS0_BklUTk2O?OuFiz!aGZXADK#)qIVXufCzz_##_T(7L_G|~7Rfp34 zTG!>QjFb{*ZcOt+oPaZ#LFvpZtdbn_r3)o5+G4GIjB$}+m3cxMySai|^-^w86p=Y% zKok80?s@>Zkx;Bxna9 z?t9xiAR+;tEP$(Xs-XzRDGY^eMFCA?B~5_t(iFwO!VP@y7(7YHC}sjnF{SF0RO3QC zjcpo`4&=P=$7rG?otpL}Os7~Q(901xbFzzbNYf<2aQ5`OXM=vJiXsLL&tCTOhZs`n zh}2{;rBIr(b*n-aKr2d0(Cg`jD~gB)xSY6Ex#gt21wIh4yYCIeB1PaVJ8}>aj*bxw ze*4`^X{gy!;p?vyb7VeCVqzdB7siS=J~P%|kslo@~woS7au zB7;1138N%LV)g{XJBSwm#hhZqgx14nh#_z>!~P5;6wQS}9WIQ+GXQ5-S7(Am&#te} zK$J2rG-||rm>2>(ySf71tE(;$C1g*>0GY^kPHky) zDzk1v9|cC!ieRGiK{*%1A2^C|uwW$am&l@kjIxjCdg@%-SA&wD%bSmkY$*pTV!6*Q848d8OJXB`@QY5EANFT_tkp5!s<@Agx zg6BE+5&W_qJEK|e0BYEhw3YNW_l-tq9FlRH5t3!RDcLUA0TDb`gb0&_l7tGu3SjU@ z%D6NHHKwRxY%R)stSD2_-+qDqs#?!!rb2T{f>cl=1PmomY5At+Svkv%<_vOSUr4=- zm??v+TDKsAB5Seih?={r|D|?%i$M%A)xGo1X$# z$~m#KO-i=&X!SeUce_d2dYwGH?6h6`dga6r2}!6af+axPs+0TKe+z>L!IwnIkGA`o ze8G7~h5KSQV1PFsg3xN^(YUA2Lsm2mn#bA|g z7l$FsBrAy*0d%}zIUs0mdCcc#q40B)oYY#@3KvQnAQwdm3pOE$Y7RR=m9@){I*Ga^ z4J}ntS!9ln1;`et9Ylg@pcZe8`OK3O7K&I`MO<*?#}Z2`xla;2Coh`^gZ#wR>-c_% zd59zOl4a=(Cu#|Q;w0lRkvO`bT**_CR+CFD`4|?0LY7HN&mecCJ!Zp_@!HUwU9o4G zXB~Wfl0Q?{xMKE|EF39?lk7ZYk&VR+iXM_dk2`<}FwuIdptR;TuJKG_Wnn3+1B{MH zHeuOZtqbKykr=rZf#QWY=L9`@;;0$~LtL;FP4Wc99-2&_JVALPsFL7~cte#0FTpFT z3o5IuaBipTVyhK2)4Xe@*Z(s8sGbMgfp?-(tsO zjsEIWV_!zOrE1|ctq->aHHyBBzG_u15TlcjKrMGusl%^aA*MFJq$dlOKfWL({cl0P zI&Qm!Se#QTsv2r;ZraO=D>G|IqCJTaalBRK3K!ke0^_CAa;h zgxyXtS8q+!{WlkJcfXZb=CDA^u^rB0DA>hb@N^hF3)~7N0YxHLL%ltaoYnaeM>v(A z6`Q&{CPma7otlj(D;W{IMZAJ0Z(q~vbkP^g-y06MLC_%b2#*Cw9TD3xWK)Vi2Iyj% z|Cb^Kj>#-hIvAyE5`{juF54GtssunSg@FtYMDkrlZ427gC2Cy-%WD>%lT4jRkifwE zb8cG4MJP^Cl~e#8TkAvMc|RpkkLP2>Yt=U3b?Ff-oaIuo*Mwafwl-!$9$xf9>mu?D zqu7vx=muKMT|=Rg@*>c^Wmu5uqCR_qq24i3nY?<=NOlls$~>i8KybyD<-))-W>->P zY`VE%uNIu4|3r-?KBxrhXZ2~J37X&wmS?gbTNf(5!wH=bF8JAw-hd%a3>V{gPMY8` zXi%Sjj;MGH6BiS%CLY)#mh!Zt)6NJ|^8-A_ESaP`2XU+%L?qN_ku`p{<%y^c0ho&%eHawFh!4XTINz0NWAa5TX zCTRCXaAgHk-HVcTYh6IZdzEYG<JTVh3wX<7f+P4eoZ{K_y$WvhM*` zD$$dglZjbpV*oK~F>cTqth&})2h^2?3}Kxs0`#*3aWF*64qS%GY!t!T-45<*la!qg zZn8~eOK)^T?fazE-(PB{Jo~fkl;`!G@~lLUm3hLbc(uQPJP!dD!;J4JD>LZnL~#H< zb@zdFD+^h2p`whhI_#Ey=5aju_M82^I#wy;c>y7715z$9XgDSFmd8cqx}_^NA%ejJ zaz(;itBjR8RJ99d;2o`|QeH2{^{wH&)ULHJkcbC3OjJ}%E17}2pgzy@^M!AL<#y8Cv-Ub$Sp3Xl1)&SCx9#1t*Vio zCg5Lw{tdT2lW;f-GYooQv)Mt+#|aZNNzHW0%NdP{<6|k$A({fqT~Ba0bEZ0w12@h# zP+WmkL?%|@F;#Mr$aQH1_^|0gYl3%>QMIVq2BYD$ zFyB~{yF7vh$f`q}o+B2vI?sZN#o5-%8D%i1Q$@4~eSK$E`CGW2BJw1jLC#MJ5bRvZ zDdfzQ6i1GB=nBqyKqMUMP-f`5KbTcHQD8d+Ph=jTSHP4cSC@mdP@-v0Bk&~2NY|0> z1w&_;@))2jYH)(Lh@z%ftG+cM5i>%ykFy2MP&sfvQfQ&oJYq%`a27Arx7P+ZJoeWkssK5J>zm5^GBILC;V5^B`? zqfv63Tj?e-M$AGS7s)|J=*8Y}W-C_*#O=*|XG0XDLkfZQU%-QQ18w*zlOn5Z(>ufQ z=YAFp|E}qLTA`|kN>pHzJrde*w8vUR!=fu({X} zK-Q%KCu(EYGa3Ye{-xUZEZZ<2eUxYM2KpK79qw8}xU>T0!NCw7{f&@M$t_QK|z! z8^D5n2H(1VShVMh8BvD|U(qn*v`3TaHZW;Uh}HyZz(5K~6+}<&+m&ymOyzRB1#~bW%e8#NnNhd4ZzgUjucwy*Fya3S% z%r0Yg(bh8%GAX{O&2Z|vC->bWqBQ|f!OLwKmgWSdqydXxR%frNYVeNWedePLHbIdo{}cZkO40(>cT$Ks&1_P zbdB~H>>E=}Y?_7|5s0^^t~Tj2ZKCXe@+?|ow56HW&NZ)W2qiqla-&phm3&iSq^J5U z^jS5yrfgP%qViY2-10UI>a-a8XCB=_r%mlkfy$LUuH>XG2_ZE6|r100f-P_DccN#1KWYR4)`4rmxLrpr_1K9%>Y>9DRuZ( zU;m*v(Y9m>< zz;w*y2~DE2{Pi6L|EPiua4Vu2Qzp&@lbi@#-_#Z{>P6jo09UK*H4#oko~phj=9mMc z5p+|W;W-f`Luc6-hk@$nrFM)0eF}KKEUemtO4(Lrd)K^DS=^1*#FQCB-@-_Nwj(}L zAQwsFd2oI0oVvb#)lj%su8O-5%j(I4=9xY4;yD})FiJDx+tX@g41wqU13h{2k)}_c zjL^R{%Pu+G7;)z8iLbX&#$uvx{{UR24`a28&g~%Bg{RhX{AsJOIfD>pBwV78x0i_~ zQw0Zy4dHo!=kK&3k@B+NT*mV0tZeX=CyJ%~)aVK-jB{HPB5=LlQqD08CG=SDN}80d zr2yjDwnbS8eh=mJJXUP0DT~V5n3nOB&qqr~qupya9*#qNmJY;hIW>en+%2AFgmd)q z?TK+EFO-cqj@czI_O9B^60l6NL2`j(iKrjno}f@J#{^8w9Bq+c8lY48&R=%R^;aUe zz~TA8ExEmIZ4+f|t_gj>=H?`v;e_&eg?<#$@yE9(J7_{PE;KbV&`ZhvoGj3Py3nH( z(@Zx{r0gW8XG@+sBpJG-e5Q3hf`mdY4@q>h2*XUMK&Xa)715X)9Vm58T}Q;cZXEID zTi=%;zLc`hK(K*`c_3dP-#;gd((7}w(9WFB)gsmYV~$h`04zCgVZ?|CrxJ5#j0=Ew z$3X8QzIHQlF~*)wm_ZzM|Dk@*(2*|-eGFACDYhtaaRR=kI}JK>(`kjR=$d844_?NTLb!TR z2qYx5rxRcn$Z!iu_5M2_6Xi&4kMrXNVoAJ!(4(o)HZa=?*qj@zHV1KxXfkGb5>^b9 zrCuq6!V7ZkK32tl+cTNOvad#Qq2&)7S%Gw1jk7vGD7%2Q^o~hK}2l% z-$_P=pk7ktMeb;9%x8a%sS%5mj1W%2kp$T2g;}A!;At@YD@8>6f9vT?{~ft<-9DDn z|Gm9uyU(ih|M~s{{lAx|k6vq4g`+}k32S5GKSKDne;vGg>nj_F6o=Ji1=(5{OX2MT zXH*J4#yFB<&dqrk&MQ`*0cyP?CHPAL2hZVBIo$OsJFb)*ah2zFnvV3PT@GZ;~B_PnwfFj+Gz}{zFEQ=9!4J%Nqsh-)%W6S}wkWdx>e-zAm?HZ^^s& z;D5{U0+iLwloz4C$>=L()Y|29TYjm8mSgbfQ3I+> zgCjF}DT^G-<_KMXtrgeknX>4uiuGJAWYvpKyGCufq-|48x3B+P-iKC$R_f@tEmf;| z4D5g^?^~)DA=wZ~E@;LQaA&my!c!?waAqdEj}D3|CEt5{9n@Y4^!j~SL0czA>bG?? z>G!2T>iZVbkOY!Yy&ObXV0Rw{@&QmTjg6A)_#|XIT%orCVyHRyXeoj*#=VIn(WJ<1H{*PmHw1rqo5+S?oBh6r@c|H2ow>7EF+9dsk67!Gqkfeaw3Nl-rMP2kZjx= z^`=DhAe49CBw`#P+vk7*crYEKi``%sjxl|m0o$1t391vnTa%?d=ev=m-Ki}lN-+gv zI;Z8P5t@@Z(DdEs@2K;^&8bo^{IpaSp)k!yXm2>zFRLk~&5T!*MLYaMX{1VgMC+mY zr7@~Tg<&I(eMMIYGkg{9> z6wVl2%+VY*=hg!lG$k|%GvzxP&Ls8b=ITS1gX4umaO<^9LSR6h=Mqb^!$T(HPsaOT-iBTW^xpu%pw!oIBF#q@$CS}k1G>4NjXScUL` zbKws~QZnsnP)+b(V)V!m!`CFn3!6%DS0$dWW^dmeN+iP(y#X+?{w;x0=8|eEbvO|S zFh;4F8R_-=hfs#)iF@^!hO-WyrWtXrW1k)L(WUI2O%b-gEY0(K0C{K--h%B3t ziSApoZQL9a}}ekKQO1uvXy1%=Enm-_t%i_~is0y#20-!3zQD z*#r%qfLvHk^`sF;|N01W>!HyT!-z}-8BYTOkqTsCuFO*4CkGc9z$aMp%Cc>t@YHaf zGmL^-#dC7v34oi8amtVb1+Fhiy36o2+)wm|9^>$wBvHW$bOUOnoX37f)FtF<3B~Sm zPW%E0Jyb^uwG-a2dML@psy9m18o^YLP%@>-RbxRBb03Eiz{e7oAM0*^G-e{zX-6xO z1EDpj^p!EimG??kqJF<_3DTXeZ3uGu*LI+O-44{kc}oDJr9iv6e+z6N5T|g2-m{Z% zMxtCZI)qFVj}dxvMY-Ux?ccM=Nb~C4@9bqb=x|P_z&mB>W3zkt%Iym`=Tw|5$`aVc zrPS1LM$q@pUI9@>y8scyN;Nll=IX`)R+n~kab-c3lCZv%QpoB%_U4_5g{C8_5E79H zh!r?sLOVN%(ovlzrKS2aJ*{BYdzsVNO^sPo6#xT4t z2*ue)R{arQsD;we2@55YoP}y$ZiNaOufg9^*&TI*foFf8R0b%KvyT+dJId$}c*TiDHR7i% zg3GLfzRAh$<0SHNBB*cPcs8TAI>7hQN1a+5amcL=a=FRBZDw^X*d(Wm8IJfsR+azw zCVjWB`tY4T23~t^Z+%{VWA+pU<?QQcMupLY=pM{P^*x5THQn05zPl)(VCuM1(U#K9q3x(KG?&ckF4&oTYc*vYFPU zn#?&)G)g)CazFzCmq}PIOa+7m;sR&F&r|5sLsiIorn{aR*!MV&r9@$!a~(%;4Pc&t z?#%|fQW{CEG?DT%*9;b7HxoTO>JKg60yddTRY6I=r^5b|)29OyP(TM}JE~m$iDM-ul^oo_p;U?k=E#He<^wDT_YS%Es_V=pF}F0U zMI||Y4>?LVSCc8HvPBT`?AUR^mDhl*88In8S6rsQgb9!n1Oc`L$X z&Js?nLgOQ_^2j!9 zc`*3NWf-AHM!#&9QGfM3r7+Q^O6`-bcjgg`UQ?dsphN#IkETQbWkXwYnjDzxii0`6 z`YExu7q`8VUIlJU;l`%iXz)g#lh(^AqLF5lfkAkb=WKgGK2uZ%F$M`G>ndl=+N4D0~z6M8z9{50RJ<|oLe+1s*2ki+y||9 zpw&Zj7J+}KIk!V1KqO{NUA?uw^+!KL#D_(K=QLDDdrnzGGP9Mss*H+>(iyS&zzn67 zBV8S7r_=>)N87h!C1KTSAW9sI43gKwHlLLZb-F3ReM1 zw1bX*eq}WmMqyz!7@7pB*|Pq<;GqfHCIuJ_*^#9>j0tQH;En{+teGR%Bmwre&JA+< zJ29=u-y^j51MOb@L`PwO?O30rdN%j}a$o(!*3j3~dVa5|Qm?mTvrTa2`%pt`Z5x2r z@@UDwPMKWs;G>>=Wr+uNd(F^ujE;6t%_!-j=7`^yrW&MLG5Z$ zf%m+%iM9({s`guq<}@j^#4K8?4V&DptoZ-!?+zdGzuwEU8$dEo9hlp=@4~h2@xp52P1p((sG^cX-WYnE#PPC31{uq$ zg}}z4(ymLn?)qA`^r%`&#Ig*(zSeo^cBnlhfg+z&N;l?G<;fTax3K{4dKH^0zCX9v)QymEG`-z@>uB0aAJT&3T}m zgb2LTe|+r$0O5=o@%)hnWAw;QUCS!Gb-eACtpq|y5}Oe^6mT~N_y$H3a*5_N$%S@T zmB{Nhi4Kp9S;3t10_Rqhy|H_#k0dHvP7X>O2k9TXlgTea*EOpl9YI(c*a^Wm$8 zm4e2*tke44mzMVNwOLWu-_h(Mxwtg_`na~P1>7SES!KCkXssL0x_)-paYr9s!|nRY z?11&A8yd%`htu?BmuV`dXh%ncdfAZDuGhLXOso!Q$k`0NpCK}kp1a*Wh!q>`g4di; zHG?A5A-5(jmmIO234g+}ODUN@GE<_QR~+(oTl9Ye{{MMKVtEb?MB42tz;gb-yZ>Up z>i@t0;>EKE{(m3O_4VKhx{wEadFGtZn8^r|G49Cc<86XMK^L_Y9=O_7OLa*c@w8HomuAtr1Uc(ez0G0JruBOl zl#?r`e?Nkuj9zxf=>kSODXNhw7xHRIu`Y0FMiVhXz5n3;fAC(VE!AoF9oGD_goDoF zN}6FJD`H+U6sl5Shv>e@P|2)tZD8)4tKa56%ok%tK)t{BQ15eZZJMITRwaN95pHr$ zI*+I9UHZNaeappkxEoXjN!K~k z)s#9$O;;)J-AKxatL8!@QlQ{lS=^+3G@S;{{?({n3MYBTo@{t~_VE@ANxEMp*GFdg@nhR8&EWdFiO1s)&?h9ePp%r6_ zAJq;9jYB#nBF~a~Dl&V?9L|)bI16h?H>D5}MVqB9&B^?NWUtsfm8`V)kC(f_Q-8RF z{%bdQ;SVJd!zmuqmSNFv|!!Nu;F2)x^?p2;e`{?F!} z6;U;G?%u)0?h+oMrv0^=xTDwKIaA$vUejpYHk*&iBRDuZ)TMavBj%{5mT+*fJ7_TL z_9{cOh1Zf0%SUcJl{Xl@QFC?2r`>PwWNn*K2|9dwiybx~aCdZXSp~K2xe6$m zlCV4KvFIcvp_=hJv+hK{ns|KBOsK1#1F}@zod8H(3&*InS-qtz3)OD!>eGc~3|~+} zlZYe&?X7CR;YK5LQNH8Yy@Rk;soq_=tL04>=O*~WSoO-+sO_!6o}=TcCI?4fzm963 z1)icWRjI!u3iOKhkDR;KwU&yD*vL5z)(8JHHp51UBp2rHQ_CDYcN?>_(AGBOA=M?Y z6Rv&D#_h#Qq zqrDEaSC2&y#}Mys*_wxpEoV0(fNlK-metjzoSH2g(gEL^E>_M#X%=b?yBB6;fzkz< z8>K;*k9A-Xpp}tLF9qi0I}f!HuA7@%>jYw_OalL66l7B*5)!xZSaJ?SW=$JkmI< z@Z^9W{rt*gPDP`apdzt5x^jP~9XSozDQP7WCYJVA;()cCRUvV$T^b@^`L0~WG;V`G zTOlDWYt{8MQx@Je%31-jgB(`2jg@GSD`}#ti%^AL)&6is=lUSkMU$pNE#iunwO1}C z_$oC^z8&=c8`yuG99P|r}u*a3YL+IeMR= z_`SaiR48Rw_U4fs;uIT6noHHzT5N*W67uM<)V0-8{#z7>zx3JbCZkT{punf~*B+}4KL8|IMTy;^l1?qz-KJxZ~GUiDJq;t$Pu;GAJW*%4_n2xhFDRC~s<;>8t@uk!uxC zM|O#@{pItEShc1W81h#y*?J{3iS+tqiQ{Zn%igXVC~0aB#TLDZb@79N#=v}PQ@a_d z%Gosk&!V6PPdD~h;CS&JqS9{p12E$E)7MBt*C#Y^#?zhVUVqHzH#k@E7O*ew&$y<{ zwVGwEm6y;Fdm3$y+5Eu(KC3#>49cCUT)XGwZJc)z2f-@J2plQYT(La&rCM|j!!P|`OfSu zCIQBpo(0UN#s!XQN#CY*ryJ(&$&(I$ek?0!* zzYnI`&&J8b`oT_9T|+`&vhjp2;e%qKJKp%XLN>n=l>!f*K_}yUd^l~P7_0)Pudj`< zqU0lvjMn@ol><7^Onmt23PF zioR@HwfVkJu>zUE>m&1GW3f({>%@n)?T>8@1UJPuKHl@S z%&!YFY>pD(dk16r7jPyUk?<1ue9yYzlNH|Q4DJjNG)kTpN}@j%zh3QJps>gwR%zg7 zjk}CyyE#;8xokBclFB4xd$-=B(1h53DSD zsBS65716N_V1@{%#nWKlSbR1rb@Ap0nq5ENT9E)TsO{U=K-9*46!cYYfV@6;A3|Rp zF(o8!m9EbV)Gs&Jg9~@}rc17Wt?dYH++7HcKDY06SUCH|_PPwJFSLU^oSQlB%{7>T z__p_~n}KBv15sG{n+Nf34A;VJFUZ&i%MN6v>wslDB0CG~ebL;J#Y<^lEt{Rq&9Yp3 zRovt@OfncYPMk#F^K+Y9yXRxZe@@9je?ZuYQ+jP=<;Doy>o;!gzVy}b{y_|!_~i9= zf(T1P9lbAHRxj$uKVgkFV-}-EoViQ91ySPv744So?}vQ{2s2`hTGNoCVNJX4Wx_($ z?4!Th=aVO?i9uyn1^j8Y!Zb4Q0$TD262`lMmTfCTq83`lm%f zOEib(U6(2NJo>9=Prn0Srur^iZT;OF{}R%Ew{m=%E`1;kU?Kh6d3S%aY5v2ZPzSB8 zD`rN>LeV;EH;&@+AemFV^f;tjk7Zjx;*V|!+ke`)O5U8N6nSEu!Oqi&4g^OzZH3|VNR>@J$;!$KqT8dRKt9pQ76lf$8>G-`zLL{hf#KXHq?5+nh~-lrHpg?twoC*=qAhmJWG^ILJG%6nEr{n_IhQ$aT%yH&vqG{#nx75kw#+Ca}B3@tK@!K$}<+7H`- zz&qmwMTHXGMh3;1B!j5(E9>BQ7d#{=4jO}E`kvzKEb{t#AZW2yvY5a|K6nu(wiWcL z=oUDlJszWr?DadBJaHP|(e6)KlBN>zBGs?ke{ALaj6lC{^mFKehkyGU_#8c>ZK-}^P^ z9!YkiS^IGZDHNO?SGsz^bIUR{3K?2eEuLaxL*fut&aIhE@*_?Pz6yo+wsQcMyMiXz zS?l(j{k)8I;JCPTaX*u@U|I4z{u+gr^=XUz%i&5_pP5O2_{q!_epskx_UdN7P4FSff|)hsOQbHz((Jn9rv&_lPk+hZuK}j9 zK+J4tNbPnZ!io={XV>TPlb$UsPo#f6@9s2E5Sg&teR*6Y|9=@-nB(#sUh|yA5yTMs9WkUB1O_id+}MnfFZNs1S+V$G~ab; z=I%SNAf}|AOwmldJKjuUgw08j>$TK`OW|w^)5%RJD#E%$SvS_i&x{yA_u@!ZW`b-u zHW^A&fc>1XJ!^)M`zpS{w3HO-1J@trnc)M|y|0B6p>)|U$NE;|w8G@>gLG)w zIr&8+j0QUFw}Fvuyakeh;Fxc@8oTo?mFUFt9hf6uoK4`qYFcU!(_hf{n(}uy1S9mckvg>k~Cp@fo3GB;|W&y zF<>)}_zdD^E8qJq9|W(cmA?jF9^fdK9SybClELidU@Z0^>X}V8E8js}; z^AtA*SB|I@l)URO>=sA)E2p86e}uWeSET+Yb}C=SFtifZ=);iTlQF}CSkk+ciPD~h zo*Buj@h?4ZWo7~OvV1V(d&#s@1INrm&S)&jEv)zo(*|RI7P(wsr6;FktfbaleX;`I zm~Q5QQKTYtMsSjTKPW*TdZpIKN!*vO?qP?I;AR&80wk$U#S^KTY-E}KlVZ!DzvWVf zdnP?G=V#uU=6vwBmlJ>`sgsCJK8n{WZot5YdlTl;{=Ux7)a3gu+FL$$5$YXWoIc;C zeQ_7|gw-@+9T}bSr!6DHEqCm__Y9xyz`4s7bIp{qr=YNYJ;{@e{ z5Dqf?Wb?JX1fvf10kzWvlXHn=WZ;vum*9WO=s@eh!ZSzsx#O^P6k5a+P|tG{#pGUB zGTF(jBkxm)rkMneCAHcmYc zb2{#%9S4KTj+8GmGn@LWq_Z&M-hVy*I)!*+ixLCKL|zAIhDxHxI%*Q%Ch>at6Q?T**~|K?pIR2wppR!YU2SPy7r5t{!Njrs1f$Y-@B* zw5At2l#Z1JBgKwT^%lC#q+QxVaLh$TnUbWka_q(U4%slOJPJ*Kjw1|>F0H&~MOeJq z$B9r^xLhtvN4sp3M>cYV&kt3-9UtVpmF3w%?Ac=UCtNjlSHSH@dGru0PZI?vE z4vhO&pkJ!%!j;i5?wE|ToC7~O49?tiWSqscRC9^ zLW5#Tl?$|f``q9^jl;t4QSBe&1a6tIK&B^*qeLLfLp)jY_aRdnj4(GCnox_`%wvZr z?bBviMmBUaCEHV{O4*S~fWWdkDAbmYtcgC(X6}lzBsnN({rnsM$5^JUqTQS|JuxYY5q< zo@~P{lp}*vDa(l7G=I|HNl3Ay{n`3(GGw)St*k_+e!9lU=|Yu^Ey8WY)k+qMloSaE zcRgeMoZG(HeO09+LT&b2aUCvjP*eC+Rn!ZJ5M9$OhJiawD6NJbpIPLEpr(PytLeTMqRihNqZ8|eIvRiIvzlnMJ3<^A)%vsYL~BEAi?v)742)n`w2-~ zq+;g3WwQbdqV(nOrUg{qlDzz&%z9B4UNd?HRtU2RXGTFf7JPDdyc@}Ol`Di7+I$bX zZ`5z>BTW?v8`0pM9B@0>%`MJT6?JHq4yOHfn!0MAZ*QD<9Hy7{oxfS)K>=K88)%V}vtI7jt;yNErU$Hwpt7 zl{0_*xYe^9ZuOBGW*MGl^MK&R9#hWc|0rN#OLgV+PN|qP<3Px*uc9wwWN)z9?S-0V zRb(_m4e{hmUnpE@!;H?i3iS8`rh=hcr|3cj*>{@JwO8x8@R3>k1vz$Vya%(hbxs4? zp|0qkoj%9m(RAcAcc|{JsQOS;OFz8#YMMDeV?gTtaf+jbTSfvN1`bYjFp>6@1ZV&* z+-=mv{K#grel(vPMi%PB6D$AZ{g;{Hi&;h(P~Bt<1%~vD9d1fSws@KY#}~6L-~Cz{ zgI%luS;!aBD7QxuT$A?Cm2IvYyYeA;=S9vpyZc{fqO;$$v!zwrTDP^A zH|`D>j}t*s)jleS0BL+mf~++4`4V2q$gP7hHASXe(sK%ZfhA@;9(k=)+1xK~+I%C&EjMS}TrZ9&jf-)bcj*HsHA;A5Yn(Q0sF|DqU_Yu%BsUpOYh&@z z+SlI=_`xf#X4!#I*mCxqEWO`fhluQDB_HgHll96q92oyi7}(%GQU#YatoXn ziEP@v7cw&j^d>y3-J!OThW&REO1>12~~8JK~XiwxnVD-&U@oH?fVJK+wq23Iwg?csG?BoZ5&-q+d%oM z^Ny3Z_|y1XfcjTJ=?(Q3gp(B^gj=(njXT` z$tvQpkgNWImVX%xcu>?U3UGKOJp7%-^Uqbg4=Ow%n^52xMsx@)H%|bt%yDXUBL1eq= zHRZT%F-^2}I1gyL1kZuAuY9Di6lU{@RuS1&4(CG5{An6N% zZ6xV+tmJm2?$oziP;<^dAzJ}5E5kJ{S{1YWnbX+84G4^C7{&gRDKQ1h*EEx2RjV6c zMYM5N^Sai)KZh%@@iG8tG+@yd)An>wQ7B=DQizi?bYWPuai)_RvUc-wh7IM-KkB|p z23E{umL)=JTJ{M1+6&IjD(tTURlxzB$WqNvqgp@Q63T3YF@7i3ej^*8J|!DDHnS== zoI3)eufb5)X8k;hj_T0u@rk$|`7~+ps9Qt(6eRV5W-1*(_b!`Xlr%capn`()_=^qa zTjew$&SdN5MMq>^)g%tK(5F(EvOnAaMc1No+J{)Cr5D2RY^s$k4cKs}JON9ymra87 z?D+IkR#~hIg+xo#>KZeD{LXifj=amJAWEmRb_Trqgj6KJ59eBaecSv#BFGcCa9l)Zli_Xz4g^PsDJsw6v(iR9~m z?|wiZexG@P;IEb(0Y3M~BOiYv4Nz##Y^Qt(gB=hFjC9$FUvli5pSu5=?9DVi8_;bY z%R!y(DZ?VA#2e51=j2>l?w0{IBg15q>j5sEv(POcrXoTUV-lHX=x8Cyn=7F@&}F`_rpb2na`So$&Ceknz>+I&)S>}S!!JFQ znUTzoWEqEvGtT51RLnTFSA4cHWSMYGEjuH=8-6E{c;gBugrrZaE>v0PqZI2B!(gF| zpJNBGQAV*UCPK~^EftSW5~;2-oXRijRcy`z^)l!U#^;+tkyw)K%8G!*pP+Y#y;JCT zA4}Vi0?4Z~GL&{_$8~si0dqL6y-^Yf2a9=&te9c;R z+@BKR*L#DqmTj70ah%D_AHqR2hsNK*BZzTTkv}94M=^BJE$WCl~T&&n$Q!^YF72Q7K(vYFT&T zsca~F_r^$APg+T6;w_qdyPBG2E%zq!Xxz{xk-raqJuc%%rmsjiFH~+YAx_ff*%_j+ z@)(n3$`J=MBF!pfjtaEhJqyKn1Md*QRF@AN>#?b?dW{CL!g|#$*Ehpqdu--3R0B3E zS=#@N@?Mx%!BHtx5a`IDYjLQd-1z=d*+J+Lfn4GV)gU*`f`JpEuvq!bO zKRvuIhvL0a(h-$tyL2i~%W*6LlY;NYW3#6VACdQ42;`@{b~{h}*_v113ftPiNF`tQ*Kpr@M;<+Q!Y0P-1LWf115vV0G>Oh9 zN?=F_L<9I-?QdcA{rih&$|PA_#PUsM9G*OBo|$6%lQ_Hb_S0KQhuTQH?drVC!mP{O zw9UuFIbUfC#EW2&lq6@W(w_NJ=gQS6sH%(YS2Nt^!SL!iEw~Gt5F73Gf*YQ5Cm35? zW7Sj|sIKv6d}lG_2f8+5FzYIpb1nAJc*hmp==`S(>unPIeiGMzPKnOQYDw4QS$7g8 zG1_;(X{=$>|G6x#`iWUp#^+Dm_%AXnX^GFLlGLTH@I1KD1a~5CUiXV!iQh)deI`|= zXJo})e_g9K@KvnaH>j22<$L18E7H)KQKl-{rEIJpPpDajiB;f{du5?G!;1eUOurZ3 zNMcs_`dz(P)Yit5zF>8(3u#KuoPz87^d)$M%$W!KB_MShSiHwb-NqaWmZ*gx2@2KS}lpab>t0 zA(nb-IV%D=&;>0KP3PpZS;wZZBD{`a3CrZDWZ1T$2W@iF8SaxXo3&5^M06zR@Z$FG zn^su}_|Sk~zRv!h;GJFWPg}zOd>5?tU+Xa7H2@L41#hi!jAG6`N~cv?Q=A9eP1QVc zeE-5dwMjN2qq=cm^K`!j$`>}KYhAEJhsSm*1Uvkiiod&=@-C}&)5W=|>cFR?tE-@- zA>sMS}0Mw=I zu=}3_i+=ptvzmG*y@cI~A09!TtcjxkBhHa=zXR0~>>E5+cUt1$g`OZ4GZEHV-0$BN z5f-RKJ0QeUF^G7orqp^;ZAhwZJkBA@uS)Htny7Qg@EB*)AR5NlU0b&)zbkIu$Eg$8 zaBFmzh_p;&wi%x1d$(zx{TFAmek)g-NfnaNvA;982HiK04q0H$I%jepd08DI!778p z?M9=`ai(tEo(1wA;70A49jIF4$)#YKPQj&-!rB+f2kA(>^vIkAh$2{mm|sS3bGq+agms{Za@RxSdfZ750gi+tiYbx zwUtn95XJXH;-t%_jI3Ci)GGPauMvj8T|9qe%BTzebXXUZ_Pm5lsH zF<>3DcnYG)kV5YOl_TUq=@(-w`!{v<51%E;YWo6T z+xIUuN&T6fA}56=V4h1JWkRuEO?z>9U49`oDkYGo#{Uf*y#_g}I_~H4Jzy5Jdbt*^ zZNjp=;-|9@@j#`NRsC_RwQwTh9n!V&q0ib+Qg>mWi3u#UNu4i1ffH+X zj~tX_O{D)=uCsB#s;t4Yv>g>b#v=&cB7NeT5Vm0r|}+XV7bOgTH{s z=7U~B%Z3&;ZPv&7CtVt|g;(N`tea8J{i{(kOe5L^>HS(8~K7nYTc7;>~jwUQ#C zQ&?QR>}*g*sKAcI{faa1vNn;lpzf2?4*ONp zUS*Z>zr`N?t{y&KU<|)l`WomV-K^#xHdkzoVjef(-a$r!pibD+J2qm-z==XmKX72L z_6_*;wSB$glEaUCef1+`CdAX{`vLKL5gF*fwtE_D>s{Lk=D4olZtWRuz|AEL9ar*I zmi!G#1}r&fQR*MRC!4FAZ&Q7Hh;#M6BIOq9o8g>>MQZGD%ROUwd&7|Str75mh{KRz zv@GhAkV+OSrNZ}REc4(&BkAf+#^)$jt|2-!@KQUgT6e2d6oy_{Tc2R{P&11PO#@v7n+y+Hwad^LP!pnqr$G`%;t~X zEBcJqf8~2uaGfC%7-qy`7gfCosJRd+@N^CYtFu%0Szia1NFZ>g2x@Mu*u3)wQ3PwR zR+>1^HWT$nLNHSPY{DU6Q+HsnUh>HQLkW`_w#l92vd4qt7HID>&-m2g1#t?cun$$> zl%OAGUfsV8m-hY#CG4WRJ2C{8)5-PqdaF8sBNCC}JC6c&_^u|k+0lKitABytC6*AX zLmYr|jX~IuHD`y*ZUxezb~UxV0>w)$&Jn-SQUCE1w5sDRseR7bZSl7@p|{)T>t|mF zM-rE;X5$%q*e|3Kb0X~tR1z3t=aeEn25S-3oigjiY^VVP*D*5Ol{&}?mJ~?H&*vM``JK1N;=ObX9la_)zPs-t8Ho^=!!JC(QpNi2c@`JT%zzPw^FOrynMJy|MOR*qz@ z49*BLRJaUhTcX-M=khE=&Xzw~2EZlo!D5+{#lC8%L$>{}`t6w%yZ6-OS(V@Y!oNvYP- zqSC6gbmqNQsn`*?VCcz>EUYu``fqsnb87I|F`4S;E8I_2V#t%o!3W00S^y5xgm9^D~ zgobr0%^vHT-Eyh#HOfxXN{cxvD0NkFB{b{^rFEkMHll*8o9lUfyswQ5dZ#6>#Gy`* z=TZfGXn7oM)I~cWj7KemIkmVdxkVYAz602l4&vB?TcUDs5YEMX9+@xfT|p_TQ-7IB zv9SAr8#LW9g{vlFj~>+_5-WZ%ye)Kg4_D$QYeYk}*Vi|f$1oH?$Rxpsfnfhl`Epv^ zE>tNQXnRm+H5Uzw?ymo|B<1(TMwMm49CMI)J-D3953tLRA^HKMHDPE0U&R0_@|c00 z#*Ohhom<{>Bk^@X2h{GcukC$!k_VwZ1a56@VPU-b*xm?z_2iWJUs@#TirEO^eT(WV zJS9@BG`xPi1;4P}VX6#NOuXYlSYz<~{D174RbqZySRlz+!z4*&7lg|y4t-Hys`-A5 zbBVX*bM$=$o!DzPq)+DhhgKT&MI#QdpefNbrjC4j!a0DFLNr5v*h})6efuf2KnnZV zFnZ7tF5fI`ScA)@Kj*kKFLNbmoRbSe81ZPFAgRK-|KjH{c4txO+OCoDndyJEE1167 ziY7Ic0j1x1THZg*<$w(xKtbTm!P%cNPcrjoPw(j)(XBkp9+X<}|w-L45etuxDn zRD>=no5G>%KYA*@e@w4L7Z4!PPOcICA}%8$4iOU8ft1 znycu9`N=94*l3#YDr!7F*2^}lOiV@j#k>&NiuY}mi&csqp&?K;*n&~*EYQm-YpGWR zAw0t%vFKJvM|4)LmKcWV^~;apd7ST3TRFwTVyfMhzbmq`6UcB>GyguaS9II#U>!+Z zPkZPV>i$;sZfKKPqLukaME0?kGyNeDHJow#)l+p09KiBqd!Mm;dGrJBj3^U-{NSkO zaXc%h8QYuv0hcfE@6ZkH%c8Nl+>>(KKQHwPd8ug)(s;(ww96zyX>p?i#rdie$R;sI zv)d%uH~!hfQ+Wye>@r1q%kgw?w>ZFE5~M7ezqmZ5IOxqX526Wp8-R4qgojdR9)Ea_ z%StFDt7N2glVM(JfA>Ly?4<$jV9h*NyzT5oKm&I?-IK2_1d_#CY>jm%=$E^-ai;^L ztiSeD`a-e#vtPBk&^IWH(FJrFMbEOD{Z@po7mIP9y0+WjomJF?LRiNIf2iP99|2JH zI0g)XAV{Zi)ez7N97{wSAU}nI)VaW)L|I>F&b2G#uWBnJccfX4#+~ z3m*yD!NyRsP0-5N_#8qrLo90iD~S#tnC^CRS5@~z>GrciO~yOB3DBaWX%N^k;ln*k zKzV`x^^FF4dV0>3pY(K{AU*e7pD?+kt4}kS|6EaFkCVByPrnxux{Gj&^kLosp)5bl zkyu?&Nlu|kk@0A{^Vd=2+7M7;&}ep(IZ;t5Uxa6ki`rOLGyibwp;q8{6cafiiuBq? z&CT$ClUpiQ5r)UzsMjk0{bWg>RH@8{=fG*zU(Qd!{uOO$$=1{lfSqyYU(r)9D%%3n z=X#`iDLr9$L39KgL-N32q^HizXAGUW-EKJH&0&P>aksJPX2n&A6>?84-YT32WoEFdiiWAJktH1D60$oT3sQB~R@^#@14P%> zL?qr=$aT6-bygL|qeZ?b&=Q7SNK5bJd%n)y$u3tMvJkkoVoQWqiH;~uVGP^zgGF!s z=xXosV)#r+<+?oz2YWN4f|WEgg}M%Y^r#3h9ht||y&})eqf*D_d!w|fUX{&lA+z@F*afEY|YBfr#gszewyd4oU@A@$TxvVDcM@V2;<|GttNITAXhtWn z^}#XExm1@suJ+JoI^?PQ@xzY^iHEh`>y{R3ThaWpf_w?HQAf}O+?~X%6CsdXIUY5> ztI};apippbrAFu};FsZ!)%dz;J)}^woG+21I*Hb=2K&?5;ix`+hO(_F<7Fz~VkXa| zGD>72ua;^U>uRer?E~ocE5J7UX;hg@Z6%wZ(w&41N_2kd{V?2q!i4F1OPW7 zDPOAW5p_5L1#j+pDmjADy5#~kH)3#yVS!z`K<*sOsRHbF9$$C8Z`^mD4z7dQT$kk+ zs5EB1&$7(Dmrmk>&!WuO(|hco7AvcdbD<`b4!s{;T2FOIh#Vq(ivwrrMOV6$9&S3C zlgcZ`c$Eov6^?%}MZ6&3eZ?6|gPYQl=*D4C_4&s{Y;|{n#rKijcE3wq+$as^sz*jg!ovGN-sG*s`Mm0geVl%w@3Y(a+PUorJMAhih_i)jH zV|f-|kJO%Hjg{u28iY<;B=ef{Gi#A9Gzu~c!m9~R`;a4arm5UeD%(zgYOv9~uqLaO z=W1vpcoEKg%2(0lsLE+o-e!y!BVvUWH}9gxgd;;>UEzG^>Fg3nd5G!jb2P_AOl;hG zehAx2^EGqp&h|C4VCp#l4eWV$qX{faq%bzY&LzcU<9vN;fAdp~Epw%`@i{{IG{*y$ z7YmD^F_ws$`Io9z9Aq*MUrP6|wsShYX<&L=DWwNU|H$IM=6seR;Sosy#BB&;J70J@ z2-SzsegK1K;NA!)C3#DBb?e;;E3Se3o@!&jy7&FH+y8P)#6;I3f6o;|7NrX&08+D@ zB}{`Vvi5G#C4ta-gx;C|32Qh1_#ar?{d(58J%Ab2NQe;z;-~%gx9$IpidZ@6w=wP1 zXrNx4WZVU?37?3wt^vYAI#yAd8GRuTLhttN@FC0qaQ7xTQuO({1RHKP++`p=1oEZ2 z>)H46Yy|>L$7(ne{4)AF#2U*rGm9K};8jht?xFFk(d`6^qNT~t@zf+hDLH! z(3h)XhFqs;g!HY9ak7Onvs=e>`sBteAq28yiZbodXpu9l@Hy zIFkJte23<)-wL2)9|LouB3go%Af6`jvKP(Q;UEf|Wi@iQ7>%1+r~PAm{W)-`4X>FT zS1Sn`MPywv9b`&Gj1*`zO|xrQE#Dfp&ya|bP$|)mNQqbHQd&w`D*@xn-yjr_gdLd6N@61>H0Mnkf!H9WT`M z(VRf>it32gv~vu{Nx#R;iC!K~W_dYIZS#?fyAu`}+lRMcj67W+U z2+8ce8R)*3o37f%#y};m8bTQ&g8oNe3LcFDgt{*GK!aRH5%wpu{|x+~>?pZKh#Xu7 zT0EfNyF96dBR$1_a#;5mavVarIm*mm1C#pa@k?f^u?Ikg1@25NU*C~|XrLlEQ3rOO zy#RD~NmF^%6(;;|FqH(X2$Kxi^I|_|7})5D)4IkS@rX%?`zplGFf!ONq7&A%x9^8`NNNNA0mQ2iG0*N)l%dEErbg3de!1cezs<}{5|6e@OpDbH5DzzoB3>O+vp%6cO~r z0_Q2hc_Spu+Kkn1g`P(>>-nzSS3SaFX7y@$5Q4K46upgOBArE(_g50hl;)L5DBGmd zjCZ*6Wn>v7Bb|QR4o5y1K<)|$TCCG;b7|_-%hvnow~+hPiV`fCGe3ESJb3ccXgw54 znpX*T&of2F_5wcXa}N+^PCs%Uc)zKiz`()u&D_yzjvd4Zt#53Y?8`Jc!CcTm+krtJ zy@qqlh9gOqc_KMVA9e>-jJ4k%C{M@weAeC?%0J|df}-5ekypdn?`h%8Br zuwtt3dAw5=Yn#&=r|_8fwI>kvcH$Ir?+^2Jyn=k|=q4Ww@CS8+f#qHLlJg3Fs#$EK zcKY8Oual*IpWFony&Z=t0?QV#s`q6h(QTs3eY2B7g~YKUQ~MtKju6#yZFGpcB~5xlTcU^S@?X|f`;d|+?Z%VhdXx%hEP$FJ*rC=BW zNCl=j_Q_Ik8rmcGr%L+D1&mY)7!ruq89Lzc990(%{ zghpOkZPelxm$#b?JHg1Sb~zgCN%vD{kj0&3JsoL{tzUD>lby9mKj|O5aFD6x6lTFdm0_?KFLWaJ`Q4inIdq2|a-t^$iJ#Th?kU_R6uXtT zf)`@xO6BSOT@lX`-F4>IpUR!C)K{4u${MEXo!fN?H?xo$8GBO!cr58zSfrF1CHblI z`gd}%=pJ8G6kGCf77mzbU%gnimRNvHi3(3ir$9osVhjFT%~GVusZz!9x9q2|Dpw3| z#d6T+8CRHuis-Oy@D9T7iF82nf+QcYQ3c#qp;qvXJvEy! zi(s@l`UTEaq9a+xR@G0gArfAiblPI>nFd+237$^EEBZoM9*T6`h}1NRuX1}jdj^-( zQN@Tu#hbeCN^~}fttJcXZDciv--)vsV3^n;wVFTS8luaiIYo4so!W-R&-9++#Oygh z#<|Mu{RSMJ#_AoTzY~6zm^$vA-hZ-oyp!Yqtzlh&6vIBSGuG%L>EYh}@BzEv{kq;w zVlk5RS&D~N;r%5t=s=5Rx2oP|j?$KoXVq~JevcwhVr~Hp4nEZ9$2o~V2kA47ZR|)q z5zGiPVlIYNB<@4T;1P3mh(6j@nq)gUjJkeF9h2)ca6iV?@Y0-M0bi~Fcl7ge%B1|c z;J~au)J6n|K4umA#xFUgVd6RmBnY>AK2x$?xT$%O{r-+P(1!_XJ1GSX#xqG>s1DoP zutZ(pLmg=Z@A~w`;xVIIZHUwtDv#>=22cr8j~%N(Ui$s0N(7Gl*&`s$oF`;XF2S3F zNGIg1f;=_;_J!5A&6ty%9oD#m>E*$Fsw9jymbEf^lmHy0J`oMN=2XzXZs9@1PaDR0W!p}#P6hFA0ik= z4YglwQ9N@%SGhRlCEH``yvnU?boQF0U+G#T%KT#$C#n^|6#A(-&f)^={Z4QR)f99K ze#WoZa_N-@2)&y9oVG*#;xCOP2=`0$VM1`pfueCyCB9@tdp`2F?oDw%nRSh#jYxeb zmNO-LaF>VN`#}dVd;O8>@F4r>SY0in;>IBS$n_+I6W0Y*93B8N zRn=QLJDf&h!~mY1iDF=&EkXANT=QJHYPPoi^&*f1rajJG*C9K;QYd3&8goTqb5WPD zm~-Xg?Rj`8Lxd7P8w(+$*v&cyIANQ<$fA6OqPXsqOuXc@PI)1%lQb{{OZ|^4<;sE% z7WO(*)=C1W8e3*k;1sX_!9S2ILPTT<{0FLDT!zE!NnxO8_j!t4e>n0+c{RB+IeX$J zR2SsuueaRCIRVIuv;Yk~WOr!nfh4D~#h`yxVRFB>IDeZkB_ex?kx_mp4yKxT$*yJ6 z?j8WpXeAIjQh{8QVq`&9yR+YIR#|8a)AVZ^UyRd?L%brgc7e27wX*XRKYId`O@z^% z0;Z$u#5}}UXoh)FH^%8R3R3edGu^xF%coeJFJMpQQgb2fQDbk>(!+%Br*Y^CM9}~! z{^$%gNqguT>7yI-?s1#pMewAo)KY!~8^k_%fLiJ$1{LqX;a9x<*`hZwX{B9I1w{Q{ z3(6}u<7(wgT&q4}OEh+$<`^9N^M1-!+e*jtHj#z}=7#k;{%>g!2l%oFM-%X@6k<}8 zP6)DEk$un(69M7SHR$L^*~-ox^k{NKT2oq@+j-jkZw=#J%P|Br&rR?q9(_aRQsu}r z)b7X}+7+An`}p|%DD4b>>JYTEj+3G2Xo))NptM1ik`c^?M|RGLYN&We()k18o3rW9 zt6T>bH4tbp8W*{sk8SL-z75_`^RQGLt#B7Kh3MpLy?(zgA?(!9H=-9wFkJU#b77UF z^ZfN-({yf(0wB4=&)v8rAi2y09Q}u}ZPdQp{p7zcz_JK>z{-^FExSHxm>TnF!CqnX zA7DyeT$B5nh4}5PU8!568#fgH4*4hj(T#@y<5*+?BDR8bkXUqe)-au#1Uy{B18iYshui)|A2 zaTy;wCKJKzgisa28Hh+WCAe^AbO`cGEX1Nb0n0dCS{2WDO!AV%ip}+j0A+-s*5*%c2ly&|UPAEv|j_s|wzu{;<+J74D z0vE{0H7PvwpruaOB_x}cGY31#ARY+C*El6F0fY6Bm0F{R!WMLh6?B0F0nJOA5;iZb zpe(kTp9{Mn8dV1fqCaOkn7X^rnT`{;WrkCtM0LnDF< zu$ucoGRgF`9zqd4)RLtj;J2vltz}@kV0Nc>rEY*D*x%$P0!M4ELCOtoY1Z6 zq}Fs=Y}!Yh{^Trni|Ehyfny)%IWAH)JNE^Yld(8I-7m~rJ}wiN5%iI2$As;RaFWk( zB{ni>L`i&BFmX^7Y>p4n<8L0X0z;jN92i6NIA-Gyq|o0qJUyTm;E0r+;1xnzPl*K> zxYC3QP@(iIu7X7wzM^~)kq4^O0xIkyuS_&a@D(di7^3gxUrHvfwCBpi&*79Wh1hQ# z3lBi5$v}7T+HF$R8`Ff&Q&{<6lMX+j{QlrM{HWHFs19A55_y=hqXcI*3AoY$7%ZGq z=}Tkw20%vE&?hlb@;Ij#P_P=$yAOfhPqLDh)CSU!H++5*Qa?k$s#t64lpj_Qz(iyrjFnF_Oy&*KFZvctJ!Tgwm{&0w%9334T>UTeLQrK6M#xk#2cFtI7 zfT?w+NL~()yy!TWbg%A7JWjq`sdKL?ov*3TX;tS4E+Be%R?sV&kSRIgW1L`V^QG7B z80YeC(vV(Uuw1*?AHRGF!}^-VM9XVpAYo(SoK8XO(xv<2c%|&(GZu?(B{GP?Ani6ODBl?y*4c-s|;}#hc+bqAaKLOYfi0` z10W9Z>OpU&W7^*lCBeoLhGVgH%6~!~96yw;g-&Y%y|&2tN^S3^y^%5@j$^rG{2OaG zFIrH)5nKi+XK~^_ATFY+bkAk3dUP{d_xfv&OPOnH5NWjOslD(W+jJ?tbiyp`=;cNHc zg(|kJ0}j`B3>xHx?+0>QIq>USH+eZMRkx3M(AO+dwjH7GtWsPQReeP7jEIGtWm@yT>UBcsMsqDTvrC-VY>YBq(oY#%k!vX{4Pj;=ug zn$Qo#+I=Y1e2TX8Jt2Dh%plC4z4`Dh4*^cNw`2d#cA_YHyCas~vo|=;--_Sck44Xp z5vSQSAwr;a6f9hLNi_p?q`s}*{vCZ(Y%yXGsB`QoLrHDi295 zUt;ym+sE6QSfo_s6TK9tI&^g*c{w!Y2Gr?DSr0~7E^hZkTo&YADa_-=CBdmnT=lQ_ zM{CZg+%v&ByBJX2OP}gg*S_VUML`jr^FmVI@fD^C`0b5ZB4w#|)6~90Ks#{nicNLr z_O2k#ZM(VwV{_o}%CIa7Y->Vh482<)I?do%B5$Pvm0v-6PpO{;%YJ1e`6I?8Wf>=` zs1+IHZ+&quf6Z7ye;4$gDX}`qb+6p zvkituuJKUGfIAPsphn#;d!|I{-o)%F8dCaU^~Ua|CF_h>Ig>Z0C?&;IZ>@T;9gtOM#(1uI zcixgl;M<55=oY7m+3g$zJc;%`x^2JcV@I3d;J~QM)^L;TG~x|g=t;UABs&yz0hXt_+G}OQy#t@%&dFG=>QI#BZ4Sb;E*pqWys}mZ4Lgg0|05Gi zS0yy@Kb>id^gaLmYsakI4jd#~8RJAMri@JHZLzNGoJ%R z%Z00~1VN~b&m9Bbw>3K|I-c3{lcIXZh{`}2qI1PCWYogK{65ZNxQbVnKDNp)DiLX| zV4(dY1m}PQFup2E%OYlH0iLz5S)Arsd%L@e#knoeYZjgVk32+5?_ln@+CZb~vs+Qwfrs!8^?;Omt9s9O^8dC`;9viAd2$hzH{~jA zEQ$ZIzjv@-jsLTIxQYM!AWwrCkT@Yq;VfJ>u#R?5N~8CXplXZrp& z16@>om4gHEl37tk!D*(HDVUQT^zoyya7Mib9i|_hJz7@05>-CpSL4WJ_G4q5EQs$u za65eKIss`vbdK@_QO(O`Q zSTX(Hw!sk1ky$!1I-0W~go-P`@eK5ruEQJZ^^i8wbbQ;i-gK}UZT2K6=>!+(gf3H3 z0t%ZH0e`Y*#r^-?j3hZJcvR;175{H<|DYcK@9E*eX8(VP=i|r0H|UC{L(soWXhO={ zoIFd#u#9J9h`t$o{v3!>!O2ao16{bTK|nT9pvr|>1J<@}^-yRFy2+oxvPH9NQPQYg zKcTXiXJho_0RE)u#e6cMH;@=h0~1U~{4G&^uJ1T>8-j=3qTl8?k+EuVoZP8E;2{y&I4~ z%>A~EzAW9ODtG6gkmTDNp3c<^cgkCHIf8Y`K&{6iCVvR)|1 zMH#Zmas-`}s9y54MkXTdTwa<~Y7PgH({*gZtTp3q;x>xj)~b9$zs*_cE$KRsa)aQ= z&(#c`^%8Bc+^{p^v-<5%#_I}~@&A;hby110(~T$l!`Z}+eOJ5Y?lzaQDKy(W8{D*S ziwUC+0m7$e%HOP7PHI(Qmbb!T%_ zBC5UF_EHp+-_Vvg*ZedQVoWc^`5S2{^A>!>Ct@%9_!*XMh@4facB6VKTQ^U1Ei>f8 z3AmgQIaoSURm(MU=h15v{@_c*XSjhAxnroqA)?wQKKL6S{*C|V-z)_*SM*!7;e61R zkuNpSD@QJ{JH>VdM2V->S580l1S~~X%U1}|d z{3@6JCY8LthV}z7R01P(>0DhWWIFjoVryeHm^o^v92I3^(Z{GUk_7tv*&c3`a+X)C0yAW0nlj zCJ<#t*-kC!QDBUFZHH?25o*Z1Fzh~p%ZmEj)mr^>HZDGw)|SH{RM`7b zN;@~vbOC*LamH1Ze(hE9uk)qDE@@pU>PT9WdvHw!@sGnBe}jXRkqJ*PMsV!@6FedDhyoms0NQ_2^T27CuifO)oX~RQ`fqPc+n_ zj<0PN20}ieu1T!ly4p0a>werpTWks4R;sl%WO|5@CSe4D#hpB0&V)JdEu8v6<4%W@ zMqQd0WVEhNtA>XK08CI*3kcLxFu+Z`Cg`uF)XCFU0WL>T0 zo_=hsxhfoUD0$|rHK+V1^edwo>h1KFqE#WH#$b)r=4BxnoRNnncBqR?Gn{t3Ds$C{ zRZ~R27SxPjMjjhhh;-l!s4!?I?vE5wzWw&wZ+%&!Dm7F`T^1NP z<$UR(d82Nh#oE4B-`SQ{`DpwO8|SaOh4~d7f4R&fUBB20dsUOo|4KVo&6Ouwu=m{0 z?$fgWGywNVrcyCf?7g=x1J-FbWiTrDOWxeL0^z@z<^N+r-G|`% z|JZQ-KNi<-dtvoL-qGC)c+lWQ>V7w1t6#bs6VziftPmNZ1@G-H_5yXivd*4qZ4`8V z%O=AbU3#6S-0YpUkeAc5p-HdSI?X!yGHn?*iDpBiZk_eoY)OLYV$hZ+vk{**c&lLd zMUiFKb!+ zLOUyUW3h#DwKJWx!1vjqr426YpN@jRKux^Df?DdHx1#;8j5u$?fI{#T6rBA;dPU!Lq%{eKSk_c#7O5AuAxzxXdUs;Gd2Fr|E7xcvb2zXIkmd9{$?*r}Ws~$O}yB*5c^Nw(O z+h+9A+%YrR-VaE8>iVrMoPVcuXBu$p)aam2317r-uGVRR+2w6c3<;e|x>LVS43vbS6H|KHy|JlyR65Al4g`2@Z&iTl5U#QiD;D%iS5&vA}N zG=YFw(ec@-+RSD5jWrnsLuqOr(`lz7iwGE%x|Xy-5CUEJC0 zcR@h-?x_MATs&)3VDQ^Vn_w+}oo7}4&tiQ=+Wic;?EJU8SBw9-`*frKeUPW&{8!6X zZ_j{T`9E-Lu?hHq>ajs9{M|5hzutYY^!wn-_06&}P^-p(SPY@wcqJx8UeGI=kg1yq z-@CpH(POEbdfWl`IHsaqocyxr3aU%RR>Uj*{~BlU^7QPv|KjZQg?&-Xvt#}LO$0_Kh?De+6wg_jIf0kDCV215;Mwa3GyU4@=CAVHjsJIx0lifJcT~Oqd$PN^|9_b0PsRVgbj;{= zwt>6HjlM(cHl#D;Eoctvd=Fur?B!H%9r#zjetA&a6W_?pItvcbr@sSeZzs`eAuUZGmt{dm^O888J$D*EeXrzA zxPGl$9qQPe+`3N9NA~N=>e=;8?l=3(U7l6>e`lqS^DO57d;156)%&0Q{iDtO|ARb^ z{O?-$xjA6UVotWvmK*cADPhTWHFdMLSK8_tS#TR|K?J&9ZyS(#RBg`9J_D-j?QQL# z(D_hd!xb$DZ|w|Bf)_u$c{Tk@LwxgfwxdUPwkv$(=!-gcwYqUsxvNom38*u1;2V>o zq!T(8yn#+LFt7V;1-Id8(q z!d(L=lPuuAk+OhS$I8TlxC^xyAa`g#5#m-?Bx1_P?1~h(3l^0Rd3DTyzp|{vNzP)3 z{W-=7DLUh*tI%xPgkBr?)e%)@zuiaAC##yFdK0#$jn&b+7gn$y8-Q@S-I+CGMNBhy zOKn0$0$l?cn$l8Ue&1!SIbc@DA)SjOTQ|57Y`U* z-Y;r)^n;Onp{#*{<0wSCqtR)pRba1y9E ztSkgN-9>-}=u{xCgP8>T+nkm7ju^R+R>y?vusG|Zpg^qCw$#(zt#PC4TeWqS9c&e% zZ6i|`);gH50#5~lTtyI6wHEH(KIGLgyTS>LzlPAUq~SGs0_+}_B_7XKI>vPw<{I$M z_{qSBkCw;Et)EY3Br6p!T8Lc9K9H;+SCm|Pcu5sn?`;$4s&=Y{y5tov9*+s%Osv=ed@%fk&!O_T3m!QAb#pdr`%!uG0eDPJ)8wHp*~iL zh0^}{ksviU$`>1C3rimoU=h5J(k8|2^&LvcN&+~B8s(wdc^FD((c^)w_5$KT-uXJb;Fo~hOCy=x}aTD{(aaNkXpF5qZ-4!1$|X5Ftk zJdsL@Ii_)ehR;?6uAEwA?dmwSW8R)PQ8`YSYlVrF*`Q53k4U zSua4&;l?QM5B{tt|6wVa-;@3K-hR#h^I-2_BmX_b)67EE$xAB$7K8xx&UatRFXs-P zD|hr;X&;(ZLp?00-Wo2UDR;v|WNoP6x*&vEnafM9=-sKZZvyv27II&3TH=gpQL(Rqd$)=G7 za=nz9-)#Hd>t#qZ!dMv-NJ{i`;S7R)v#fnxZrzq=EYs6%8+TqhY2jCfEvf39z}`c% zsnbI3lb^w@8lJ5S?1ogGD`Q+V;aaSZWpU5=$$W?|smlC_%V^ZCwQ{3n!lhHRZP&b7 zbwDhv`xuT9!38NU*$0w+PZMWcDj${&Pz#&}e3mh52hxSkpHau@qLMYOg^@Q8%mRIQ zCE~i~#)`Xd81jZ8Zy551A#WJ+{WIkHW!Tr&f&42!tLpzsZahoFuE8Ld-T&;?;=dj3 zZt_1q%=57x|E=*dy2&oIdM(t^T(dzL=#kZ*Pcx_jBe-_-U^~+5E%E(=H&as9tKu92 z3*0?sW;Zt)gNSsX#=;jw{25VYT`I!@v$WiwS&|`KR1F?+r%$Tkp2=z1a7)kPa)Ghcm)}=G$mO~3a+Jj z`)0+9etQ&<;kae>G0RF!Gg6$Xcq+!bSATfDYDPJ$8S2nQ6XPM)4$6-Hvf*QQ)l*Fi zR@2KZ!$_IRHJ~HEc*?m|fvtyJif8htmIp(Gp>${X`aWpRLfgd zblZ|Lq=a;=e!4(?@5xEJ=}ZR5B#z z7+uduhDLLmK*G5kk3Zlk;Ze{>mov&Cg&t&0=QENdXqvE*wAZ59bO){+OL|36j?0iVhfdw?e;}7#3O=v=R@J+<8 z^Wd9kgg*q|L}i)>-~6wjkAA@gWpj>BU!3qD$_pkA+CfBPf(No(!G8TSa>-8X|NP|m z#jBGjjn`{q$@)LqKdh|(y{DV=-vd2;RG)A3BS}*9T#!Q$oMyblNx~uTRM_>$Y&4wF@YC8&oejoinN_0+g#%allTXdRDSn%)v7KW%KH)TSz zGK?t~vKR`({|Mf{e?Mj!X9)?!mV^qDGlb*V@zoB+@o1jW@)l{7nm$GfPs#=YQYeYv zfhy%D(P9w1yC4L;G2d)w#jA+V1~D6R_yPaC6NLubXu=948We}?n2qOQ6}-+P9&2yA8!nXZv$K+;Zt}qgysF5K+->^u)t?4b8buyql40MJX11 zW%f#jt`4HzXg3Jt|5{JPn^^7Dezd!TI3be1j%PTVl7vlHXQ+cw!bSsyceo$z4xayb z{Q8HJmp}awrLml~6#pvbZYDj}Sq6>b+{D`qIgJIeU(Sdg@L2UXW7kl6!o)dTbX0*E z2EjMzMZxl%#9B0j#(=u6_R*I7;{dS&i34weV4?mtrv-_(gKv-%8t?*<7*hQ1>cA@wS*OS5UqTA*23umKRm zJ6IR}v4t0wGa^YT909zL8hG0gx@0iPf-f27Dp)?tr4JAAp_r7ICSu4z#aD{4t#ScA zW7p_Ps**Y`eYxbt!h~;-YYA*G?dnn+(EEDj_r`Gn!d{RuNbhn*ysyA}AEEDV5hrD7 zzSaUQN~6s8JE#==TGG}vO%fHPALM^gNm`LwB_tdM3@TfC{rnGP;!|u#!6h@vk_eI^ z6L*jk#*xTXu5(AAtt*NVM(_PwviCr~k&HU#2$jK=o~^@Z{~+4ku=79AvjYE@LgyMAz+(P?@Z_jw z|GmGrySw545ApQT3+3sr&J;e!^hvPZ;d>~6g)*N*516pV8-VDWU+-nmN3ZAUh!luT z5KCe-qa5@da6T5>i}(wRlQ}9_l8o^9Ltv-Y(SpG&i!tD&6m|A?gMwU9(di#0Oo&M^ z6d!_V!bUh5!r)#qaiGsZjfP7KW%+G^+rGwH?=J|HjL%94oDEPf^5?5 z4MI(^mHLhVV-eVE{jv{VSg{iLCcr+(MlwRE?+jdy3zC(3%u9xt4tbzg5SlSw3WPPl zbF;K0L23O6scHox^v@a5DuH+hNo!xR;;ZMtl4+fQRF1hT5`+eGV3(YO*ps*55aLuy zVpPrwHlIo@EXOI)=|<3KUiuY&yu3WShyqWqFoe(wO0@tn$aRN@>Kt6>C_YbVKCr5& zoYNt?+Ku)OLGVS0&#+<%3z!1T(@V-rL7O%n3>qgC;bIEusH}c-u4}0Fs+hf;%S@+YvuX4QT92%s=pf8Rjr&acDG30}T>hk(+YR=cEKZ zUiFLE=0a?%29DX>g^$<}ef%7FUN_=}pqD=SCqZ*Q$4PQ)&G=BpfDv;wkb0Xk^4u^7iJ<73L^Tmc`?m{>Go*v0(MIVmE!TjPW?L^H9DIRTwpHYQv%k7Ax7 zCYJjZEm$UL6WDq}%Q7Jfx znu$U<{&miwL@Jud$e3`Bi(4=sP`5ksx@KM^w_@qS2u%dTd2EOLab&=zR7YItBQ(zE zVrSb;^^26GzyqEfzM_Hbr{p(^U#kjf;FUhue?@~pu_=!MDTi};270a|tOhzj@IUr; zcLP7zuX<&|@T#)X=F)~ENFbrwPuX#joMt5{CKyOdwO0%N)Ev9ICyEQ^Me`SPoLrQ6 z{6UtlaZpj_dz{jQN)6)a+0~QY4if(#DwUxsaI1M>@2nsbQW(gv%UqBWVf0#N>{E7W zg1Ux5U#HAOOxk@QH)eCtM;Svz7RX7FQXympnG2|a^#^@)Y`KX;)2=u=!%F3BrP7iC zRhjIy0m-gl;r9EYjjJL2fzU6<=cmWty*wF8K`IFBKnrY?D|PFbR)d2X-r(3Ji``kg zvf;S&8@s9GELPUYAos=c{)5HD=pDRB7X2k^7nYL(m#k=f2iHm|rrF?yd)GI>IgQe| z@5i;0#8(NQrFIMJ@^4}ujWDO<<9RtV15x#U4}6z>#aw~;+b|P9pTC5yR^1uE1oIi2 zC$X5~xtL(V7GW9CY^s<;U#}De#SF7ONobN(QA13ODi=UC=#+=!F1uO$rY^XsHu+3CX77 zxM;A0%k_ssQS7KM0fY6Bl_%$i!WOK&>*)5;8H@Gu(X?h4j7Eg$T|%9<3o?nm;2zMn zVk3(zpz$-@8e_K)DZYhm7(|Bx^h1cVlvLzVabyt-sQ}%@6)A2}B9?`sczF(=px1)W zp6D&mMBGDLd%L^4f8RzUGNXc%B7sO^_>mkWaXxi0qZ}#IvlxwxT&@ns{F+L;R%zkl zM>`c<%mUg0;DQO1R32_A;X*K;C)Pv;L|WHi!JLXKV>J_pfS67u1QY<0HYNVw3#2n% z3h91F4Hi(UIAM&4%YbD)gtJ?euxv^SrM5}wbXLk^a=Z|om`we#1f*Jd#AHde(NKRpt`bmRuLD%uqt&{2{&36IO7viaLr}d zq-wWz#$v=WH41Vsh+4%g*^09XyE|*^GHu#dB_tyViE%RHBMx}*H@s%j>o{tchSaot z8Cht#5G0!y%UjNS$v{6Tb*BX->PY!38_69>tVo{#)pBVJg&Va>>lYyI4s^B{-ELhJ z)pD_udI#+CVS%WP$(YRP*~da^?hQ<08ddE(EmA9WFc|kJtC^Dd#xP>kr3{WLxlR?Z zS4=S6Y{G^O#eO$Qze8}xOq zRXWNDiS=Cr&8#-j+KgddWiyhNz)lEsWh2Rm9HRS4ZGlncAl0C3f-Q_>V=|bMOw=N= zQ!KDw(aL|_+e;~JdwS-rDp;(gDBajNVWe4o?<~QI3tyGOGQDHn|jH|m%$@Ks4|NcK#ZpgrlmU3%NDp{o?BDMv`>chJRrJSHS2 z@wQ%QCs|#{ZrFGlIPREb;sC}6*R1%EFdXxNKtcQyCUio^x8sBi`r>Ck!%7mE(4-{A zxkSwA!S|q%J}0qSLgv=MPPHN+b0;O0*f3V;;N}Jux^m;dP5cI2DJxgvDnJrzJjVr2 zNl6N>Hv0mn+~o#Jb0c}W)B@&t0!v)$Go%@^6*Zc+HHgBHV(=tRZiRfyjl$oC|CFc7 z3Ask%bP?ksb_LW4B}vR9*o#>~r!=!`?N>GeDpePe0x;zWUtyX6nO#u~1pV$^nda{- zFManb8@&Tz%$za0%Oj4F0N59l6}hA$rKq01ldoGfW_|R^)tPk|d74cLx8hW@%1ui` zUs8Ql(OW6@`h~Ps6?K)`yzWU^r5DVaySlHdYDaBtP1Swi*Py6c0n^TEs{25-QA^eD zBkgA)4_4BZ?xGYT@$?qai8x&c{#%=`^qhgu#Lv?I4uL(3`K^;f-29vwCNe~OK~TMJ z{eqXRU-GK;%U-m8k!x1>lC_W2mFfPjzB4tKrboNYSFGNptXtt=*STd?t{Jkq1%^CY$v$cslSm&y6#7!J$}C^X$0s-D{Se zGgg`bf|oyYQs{C)tI-1(Jm6R`69M9Tnh<`=OOiUHJ$9l?>AA;oY%QZM{`=zH<>{-F zASgx{q9pZU;N=jjA^#t6+yxII>lvnW=hX>rt9`~|qYhv83%onCuZGo*!0vy{Ih|$*qp4sra5F!d zP;klT;~9zP33%Af6T*XjKhPhgy@k@<9_yFz1{r_R7xRK<(+dOU6heBa*C#h*JO{i~ zEeT6!sk$UZ%Izn?av(g(_i%u5Kgf?C$nDT*sv*8vr3gY=4z zRYA?Cs3}By(SCFg9R^|ma50_{^CjbZ5x*Mi{N7>AQcSZ*nUbnP>1`BeJd$-IKA%kJ zP0-iomj)CVp=B{Exgug9=^^Uv_q-w`&iJms_T7aiM1FHgfH0cfjtUxUA{0EpEiFjd zCq~AHLUNtoNKwK6D0(nNIE|ki20_UZQrHy{A_DiAL-d*{o*~r`cHN$cErI*EpB0pW zbdzAtUxVPrq;gB0UHT}m7Exx^-tD8^B#haw=&Wx?E5|VmjTd|rQ~rUMxa5%xSQLjb z0r%Z!|KC=D(@zOw`KL+zX>0b0f4bU^-{A1~x1X}Bl>E>C8UH;K|K!`^-$xC=g}4T; z8gj2;#qF~}G0z;ONwpVhuQd16u;;7gY6;hFn!1N#Yxo6r6WFUwZ#B{Ud#WP~|A(Y5 z5E)-XC>WkcWF6g&{*5(c0o zjzl-@$BL)NzsoPLaBjQT(g)t?D)R60_2SxNF$B&XEm$xbLDG@M!~)YlDt;x+^vad> zImB|%H;;nLk{ct+=~vQ13*bq zjlB4ijX0<|O*_<8L&8ves41Q+9Gs!7QhvWeQJSxJS`Q0R{Yxx{(~OqSEsc~62^o#z zL~D9-7Mtbo7gooPK6**7NCrl|1see!CF;$YcmXF?Op|#*E@uVdGnT|dbPymAVt>$? zhs2K`363>nRJSfwsJ$QwzP%u0mc?AE(sEKz7MoXl;vJvEC9u=vUVz{Vhs{e{6nbz@ za7_I!6#CVlqYvuJ1?d=~a3yX$`I^u2rsz|%~+V<$5||M7t1lJ$3?Cc44k96yu-vd(nN*C z&S>Z<7o5ZkY^>F~zIsvX3io!kcF}EtX3JZ!L5aJx0I%vHb=~1jX+~3=IM*8m0bh7M z@)?n50C64=ybmz?K>M2NX@3zd7&D_zJegV3{cyV5S0Din{F z+KeXJ8kNcOPc3EwG^0NIMd3>b?@$khp!Wxie6WK5zs&;i`lrj2i>SOQyS1?_|HIMK z{fhtp!NI}d#{d5zp1lY{a8}~LfepgwR-12#&z$I(e|#9I(CKQwU9hlXqu7onJnCv?WgR<5FzSB??1pspo}9Z;eJ!$4&Jy@4q*+Bq?-+m$;ns$msXQ zJN4r;xX6(4jA;w8M+(Q`BQxxQ30v>Y;7f+%?{Fd{m15B>B80^9AEMI|4AM*dfhgmU zlxFkN*479ZPLyf``k z<@9;OL<0CbPvbh+d2|2xqn`4_8P7;LPm0;6`RB*AiMQPvIyov_7fTc3zeWqjakay8 zKKtnf1R|{r9%#$%VG$&#*KBtEtUEvnYo`uJdrJsd(YXrs3i`g!pL?wf(7O?EmR#=_ z%4w-5U~m6vv>WY4d&7V1?e1zbT?l;QFxsBdb&6pV7>5&9T;n3XhoO6xs% zdp9FVP6{5Cd6!U7i{-z){iEI8iu`xDzqgV99_0D>aqtbgqUjJW(k3(^g2O*c#juQL zWQe{Qz&0`XCOEmt!8)p9Gy`N41*%-AYzddLYNwY$#uf7DT5w8j&#; zTpJt`9gA1#B?Py|aH<6ZC~X@+IoeWwBrjfHh_Zq$31M_Q3Qi|}WNEYKsvyf?L}V@Z zz;zuA&;;@d+wLQ68IvAuhwE1ZP3b{w{oZ8)R5m2>_ao@c@LBg6wm6z$kwB_@v&7ST zxgv5p{MX-j2m!!6ShCUX^w#{RM}^MxiZ!HcE5+2p6f1g>rIf{U_i2V|c8;rkbBA62 zt(^lufoe*K7`?yuQ14xD^+hIQZAo*06;Rgc@dVB~#x`^VGOC%xdds>T$)ti+xh};S zo{~5m-O2=QdUloNTyrIpAHarPsTay|QHE^N5lbh7SEusY9qgq^9;n7sX8vl5QddXO zMB5ZSN1xDdb5@cDGWD{F88Z!qtH}>mujjB3*9Za<;A$7x26=knDlvuvD-%+6$il`k zG1($lnzQx;!_pr5tG~KsR^(W9I9RFI_2j$_EZ#4s(>7N&-mT?h;t06gZNM{k5FAdc zo9U@PYDdaO04;QKY2!MsI%H6-ag^Xo#AjGHIl(kh!5ZQ9gcTzi$0Uo;&s-r8$+(Z; za8GdDs~)C(;Hq%FZ8t`u4$xQMqXvKD!@u$W1a7{lqTi^K7Gujy_Tspg+Z7Nco>pJ^ zU1{D^8}QXAeLYMal5&t=6xz*u>Fr}LzwjuOPR zkfs?Lvvfo=ot~$2o9B1M`M+;>yB@AsL=%}x!IOne=4cDt{8^a#@YWKNdWSW+YYQ0t{lHR$o=fP z;emA4CY3opbmD{OQn#?8b#N2!T>)7Yrs{XM<<%;FX9hscA8k3Ps$ZCaRd*O`0p=}^ z@4=#-*F0k)beiRJ2^v{b$&nM>&x-Rv5^f#b2LA*$76RxYVNJ55=hLxYe1I{kCCHUz9<8 zZT;;-~2LH0pUHSirW--mC3%7w~{QqF@pu+#393E}%e;?%egYp0GBsqE8z&$czXHQw# zt|JiSeoiJ3v_Wyq1>FmbQL(eTRqszk8sFM#9x4d= z6m>VMGOg$-M`&5lCfM_e3>UXDeg!y8qAgam-H|T>w^%yc_Q${*<4VvdTe2aV)Y=1G zpV?ZF1yi;mxPsMIcZ1&Gf^17u-!=Iz-Kq;W9hut#sYpEI7W5vedS|RC(eRm}pKtee zcbl#J7YpsCLC1Z+l2}te%6Gq0@07Jg4JVI`7rqv)T$52GbC89~+34Au zM_WJsbaDCa^lba>@Z(3HG#en-*XMCD;nAVHu%lezAsohtb9PydRm>P zt53~Cx3RK}fT3KY1kpE+W($?eE80gN$n6e#BtxbSpIPF}uIg*RNWP+0>kx_W5}spu zxfCgT*SmP?rGVg71qa7w8f)?U^7mT(n1v$K5-#u=^XuvC7DIIT{A}5BsD=7)=Cl?t z!$HBT7gdW^k@BK!)CPIQ#U&_f$=a%um>=Aup^l=zcRT3O3`)7==l53iCUPLwn{&xi z0yOrNsPZ_3s!B?FtrczUn@l}2sJ_*7vuGc%5l%jSMDsi+#gbmj;#Dw>aZy_7rJ-~c zU?rYPD=4pA6-Ye*ODmje(JH8F)RtC2enB5hJt~cT7A`lx;$MEfRl57Hvfx^}z21te z0K3zYtH4@+%~b&1VbN6}En0Oo5!8mBsjSY*U8=3VuC#gO1{iA;32NsfKO9=~8OjYS zX0wJ=L01n|4Up?a{CH*eawwD7)Q({d=rok6O0V<+rZ!mF+;RY5PSg%+y~k>zKsu+~2fL{$B@c-6Ku{IRsS}x@`shKOmMVyxF%HAcd7+qph$ClOkq}T2; zWg~RGNjD1GLlRchqitb#8UW05UKRwW&uXz_>eBt7RvU&dGyzva(a5uJzy0>xT1m<1 z!wF5uvjHi`gUTGJ0t2TU-0-g?Vm8BEAd9sl;#696b>PqJ;){fZhf>cIw9@tv^_(Uf zz5YJgPzMXZYnQywp9f3&;CShM{ybb#xWrTR`SXAB8Qwp7vaGP|Vz0W|Ys$qxQO|3g zsq(W%MQJy!XN?C-N_W!rwnD6}L$7i;BRDB%zxO(7ZCW%f`iL)MEcxH^HZ-JCK_RgG ztberdsC?9QiLG)NcD$LEz*_N~yb!}X-xw`JyS0I~UUb=st#s+#8hVaqOB~Vc^>{S_ zmDg;?%U{nkZUyhW7VNjtC)D9O-b+{HtY+uEUl>vpRSKS+zF1&cU~<`*A|`>&3MYc) z77Qi{h%ICDg4}CMUaAgLteTSJ{@Qbrbl6gr%pGgDrt%jJYw~7At!3it`$|bEl6YmC z6oI)a9782s>L(m+HXY*4MstK^Nl2zo3PWZc1W0PlxE0 zj;MZD4F6Q4+N=laf~HA#;^Voj;yUA6R|0iGP*(G?vnf{Az;Ts}tW5Asy@e>JmGbMDHrt}LJm(QI0C<$dke|4T>+k2*Mx>FpZd zzu%Zj^B`-7Rly-&ZmTO7>E&%s43HB5seYVh`d7~4iwE*jTS-LOJYVoxi~l>(rMesd zm+=25hx=9jfB5w1Xv6;>;`!JmB3jEn;P9R1dbY50L)cTKBest*D+uG!n5BcOJ&0i^ zw~;dgEvrEy#Nm5o&D@{b?w%rpni?kzz}!?Jsr z9gemzbPFLmeNgvcOT4E$j-Q6&MhCRpTEhrF@kY?Q9lxiIu%;U@wAQc{LkvN?`L8Jo zbtSg4#N*j3wfdAe zmpFCr)+!(FwLM=#P;DvRc~9-g9sN>T0=dl2d3|4P6$At*nz;_+&dZ0OMVS@mya|)q zr#s;z#qA%3?^3tMMO?p6+eV{}1wfwDBLCVn2HC za-N?kO~6+49OrmM6IxQjqvNwvbOm4E#62*iov6d)xM~ z?*>Th967SC77#?ao@zjy=Fxo|=T)d*KdA0wa#NBFhUf9yz)v>%IA^goCWTR>L>5x< zbH(%K@thN{rgIeRco1w4_xQC*aPxe{=RWxVvKYWi@Bj9y`roJfM;rT}2YJ3K|G(cj z!S}Qc+{-R-_fWxi?T-!OE4GN1Fur$Fso(u#cC!n8?Pp#7-_7z4yJ5#Pf&Y|9%a759rtXOPG{&iAAgj{0oF4&I@`)6EY?7P-I)3 z-sXfyLB*veM30S``eXe@dsCPfdBMc)Lt^trTHBkSF}u#LaSlf;B(t2IIJ5RMAxddBMB0ir#WzFr!l8KUX;tIw}JG2~Oj;=|PL;ygX3WwQumq%w?oEmX*<*a47*jWSctL0=Qby zgz#Hll2jANM()thTKm7_Rg&W6qQFx9-;-MYufu~){+9=Ny7)4+xq>{``#|%qd{=L! zyKd1Mp=vaXA^P-pU>c40s9M8zT$TkL%}dvn^v>x@G@zCUx4P}24l8cGNcntZzpjGvKoG-|l|R|8IYPlmF#Go(lihGuvYEX;{C~B>g3h zV_((6zh(Ss{$h@kixQ7N_#VYH{~o6_p+w%eR|1VRRktOAnh*DQt@Sv|CkwlY?tEjX3dN@uFv_BL|zDd@M2 z_~?%dY&K*DmwvuDXvi=5uk6V$hdy}K>y;YJzzFZ$eN|HmpT(Ou7f_57blM^88Y ze-H9>)c;oUP^qugT%T3(KwmViDJ67H9C-(M;a-eB+64xaxaD$ymUO_0{k4ypGx2Zof3Er5h5vW;0bIiW5BGPg=f9&T2b=g05AwA8|E23V|D|{J z{8Kx}*Y^JVL%9B>e?Y&#bh*=Cg*z;D{7c)s{<@6K7jYK5gS%n+C-ODyM0@w|21xGU zfaiMBEg1ZY|6P}keih%hwC?!!JxxfX+ne}sCwH3kYkIY%U)G^3{j%O%=|j0ZrC;0M zDSbF+r;Wbp5Adud|6h@!q+^`qEXMPa@i9)wf@8q4`=6tl{^#l5CjbA#JdXZnnN6Rb zYA^0nZyRlCHloSmjK#;6EZhX-?g63^oH_;k(gf)*aoJQ4IH@G_8dGi_&4P?snv%?B zC3N4cEQ@KI0(Aid)tw9m(j7_5Y39T0);!@Idm$hdEJ;XwUe5>6@bP}PyJa{jg;fJW z^2_Qwd~=H_z5zQoH(IcL_Yi$fX;#O;yjvRsdD~^OJA11>&-p-^yvViw0*j;K1-ZLF zLdQtZ^5AyWU z87@mwWE_3e8nkH-{l_WHq?!YOf zq*nwY(>U*O76*Nl5tW&2D=)}|-jG->*?-%P5M)PV8PpUgp_~*bp&5yS=*7jmi;@*2 z=%eQ>O<9J1d47RnTJRv6(sBU*O7w$h^m{RYfAx#mbRhoGKlxQQuz*H*{9&G>2~7wO zzKQsC9()sx@Q2`=s7&+VoBtK`(J#25Y|hc?ixVD1%DO#>XiV@xmMhq=e?|_l!umfy zIezi#Bue8|+gP&xpBz4|+W+qDZSH>`=INs|GvDa_YZeo9q9*)(5IEm78K4O(&T)_j4XAcHx+c>ZmINnQ(DgEBe+{|$XgKeJKc+I6oc5o zNr_m7FnaS}M*nT#cq;L2q&%LLBStwR=*^ELNo8?991O(%81dP*=9nnO8J?0N3W9zg z{Xj}|Zu6R*W)l_!@87>4vy8KZ1fn)7NX`(BW2c{?5bx)ZaY#4R^f6MsrVQ{1CdItRY&-{_5^!aG2c104c!`q) zJr}LlyGPaMH|HlWPmV86-n~A4b@H~cv#or{?SVAyp3A&JZ@2pTH_fKoXhafrE%1_M zT?CePSC#9~A?6%Ldr$u!1n=82u|>_^L+_Cgen=@myf2G6c`q29x_a zY~dydE=pXKVrV7>OVO3O4?T=_qum{tr}u)e4QG9?W|MN4#W=KJPx`UaK-j_YukT=p z#w;6?yyOFRMT#p*t_Lb=cPPjwWYRx;Fa{|olmG|Y5xSg_f=q-Qc+2K!hOda|k)$uc z$+FIoEvKd-@+d%HH7*lf*;Wh_aT0viy2d@y2`OqTxDM+$ok)Z^AYLGLouToZmn=nZ z{Oe~syp3B*$Z}HPk`;~bMhP1YQcSbK3zrrLIHyq>_x)SaZNv)n#)iH9k}cbytlK*% z2Tp-V#^(jhvmW$vMreWF>s!V5XhKO6bA-oZR)__k+=}Ahqe!k5J*q|Uy`cAfxe^Y< zN_akl9226jjQd5i=@2Ld(#uwIqA8C}n5>d5C>Q8hX|ZwgtY=65k@519J=aTJg6;Q} z_Y&+CK>%#$IS>K;U9UahXy`jh<7#K4*|dct(X1@k09Nih;7te8% zCSyQW^2hfTsP7~6-K}8IrTJR(Ws`*RUhrQdU|!Qiu&6v?w+JIqMP5oep1!(mMHxO1 z(ild;B{NQCM649T`3}4xSxLvZR2-)uzs(`L%GMRd2&4DDz2tk?$;b^(^Mq{6$;aru z6CLEex`QV%;`8wgVeYJw^`ojZg;Qynmr!hxI@%TFN^;^|p*K8K>VVDJ5CKlC4mH9yH#_KSu@5#=lo{O%KX~kU@&YAPjWm z7^z?eaw+oY&v@eZpB2vkV>%Fjg$_&U*2XgZ-|kT@{>S0Q{_kNPwI+vw@51trz1{u5 i$u+kB=cn||vw1ep=Gi>%^Zy3`0RR7QO0C=gz6JmuJ7-G( literal 0 HcmV?d00001 diff --git a/helm/atoma-node/charts/tempo-1.5.0.tgz b/helm/atoma-node/charts/tempo-1.5.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..58635bfbd3ca8cbef0a4a3170e2c0f33697d2dca GIT binary patch literal 8437 zcmVDc zVQyr3R8em|NM&qo0PKDHbKEx4V1DLbftB)AtX#->=wWB-%iiZWPS&-KqqCfBU8S~U z4B0b-h$I*QlsvI~fBO|0013WkMvAO#cEwaV8nV05Xfzs)Mgvfg6@2t;MTLsiG)q7E z2+w#t9-kZ?;s3|uar^({$J4`4CPzmn)05-l$H&K?j3?8>qvEvU$dNw zHTj+=5Uh9xGqO^ukh9TfX~3f#RKQ|Qz;{vmhjpQI^g~US@L;ld=Bqf0p{4 zE+!yE z%imI7fUF^Ck@LX(jg<-{FGUQS{J(9qDWaxb+ZJQuEi$NaWoEY)c!`O zmozOwek=p9x&KecM~9vMe=;6F^#A*K_DL9$A1*-%mVhK`1tivk!DkYc6}8lTo{_!1 zAlQHr^QaYV$fFkvl5<5Qs4qwY3tFa%{Jorm$U%W5sP=?O z4WQ8{@Bj*E8~B@rtWc0TOZ*5VF5den3-9({xQ_ z0Wn*w3C#%=g04}38C}9@nWkqD3s5pLSoRmmN~y>kNJjG#X~Y7kZZR#t6=0jog2rI+ z1T1Q!eNjm+V%*wfDlg6McNhQ`Jq_6+M z`da>G_=1p-Wb4-!^6#)VUtej33J58Sgeo{wf+|?9XXG_cQJk)VKI zOOQ2v5*Ou+OvdAJ=H6tGVaptS$IPo#w|fg?+J|xS9n)-OkbO@%yG*dYlon*EmLOVIvBK&n40 zo!N@~YYBoOuKnv~sOxYcwA+(d0_$(_XP#Ix0<=qH9I>Mn()}*wbD9$FY;Qow5?`HH zuT1L|AkI;4D{~t0*aHDiw_{qEd!Hr3$AJ&FF7Dr&m%7 zLgUbaRahP&8pj~z`30RiYzXVK~74^@+Faa@Qn4ilxY2@DGp(- zJc%R0?aDK_H!F|j65>*TwM{oF&zLpB7M0+%tDzOm7Eq~1%43?^m-x5S0YVC@Rx>iX zq{3>VXZwc=F_M+Q!rFG>z3(#7P?w2g_}Io;*PaEwUJL zS=xGXl@_-W!JeaibU1muSAh)k;v+oS!;v5^1yk#1dLVqK>_CXtNBi}sf?cu{mhfE0 zG^J*2tX+nEVic66OffeSqNA1wUU)x4^7Q3P`%?f-e#q1HYtGfTECsoi3bM*M+TSNn zlZ2TbO)AHhP_Znvqow;Hyii$8Qq_>;CnSaQ^!? z5*BWV#QJ>UT=-omRSth2y_Q42fmC}2&_~Unvqf9KTT{OXno4ON3w2)2+Rh8Xwdclt z71Cz)Gu=uohHxopv67#)J{B|&Eiv<{L)j~)sjM(Q&R*lQnKho{6tIb? zIVkl+vfIhqLTFAsnpKsO$ z26B0xu5lWE@x(RQR~611Tsb)vuwd^D@`~G_ps^6BV%9F$dAwcy!QS#*dq7AD9j8J- zp$&rK1TM91YHCf-B_eP1+>1$4N?4SsnRs4taiOQ4S4^!u>5(~=kT{*S(@^xfw2S2& zGUJk25qw^GwQeq1ySa{#X-p~8P(Z>2#HtH(ie00W5Z3ItTK^zwfogevIm|>y$lJN| zUsa22{k+;9+<4rNT| zrRIz#LbOj7l%-OSU?zB;a2%)12xBKXEv~VHRc#pb*J+=KGJh(6lptp0fUkJ2*|q`>Y$tpg$GTZ&uEEc6&|o07y;3W!R^+S!>IT3$I2R)z_AjsT z^mNX89ol)prLra&#dX&;;iP)84x>Evo2n&y>vowH z4ehL;M^eqHT}6Vmb`V;=mft1%Rsp*dx+5bZzr^8e172giqov~5Ynq~a!!ICj zwWDr8lOFIaE%l7+#c3}W9({-Uy;)6}b=@fKDJc+_G{wbRqIp()OL}@3|rv6b<$861bT2_q>`Nh2%BR1U>d2@>tW5>^IQG zX3h)uytpnRm8rG|5;F-kA7<#7AsEJ*%<_TC~V`~>YAyi7X zMDrVI*KecyT}lw)vSbO2a-KjaA%)m1J?P(OV8WXoUvGE{!qdx}mKgIqhC<2KN{2Q~ zDx`T5s;YodQ2*^$`srf9a;DaIS-v5G(+^$o(LS|>AWt+`EOc)I_lGZ)mJvD?jH7|o z8Wt4(uUECr_@h%Z?&YzI3f5@K(fJBe-9sZ)+*vBnmi)hyljCtK|L5rOL;m0WJn!F+ zJ|~yjvC`x2f~BAbkgqZ=mUsm-^7-h)hd@IG&)*f)EXFiC6DI#efrSf6V)AI z_efZO!?9nztHLpUnh?3x16v-GlSBNIWoPAL!QPR*uqLMGUixpt-817Z(5ejW z5v<9tB~7&}lMq@I$afU{0}OS9SGo*c2TA4-(^3MFJOh8?kF~`)yd+eB@zudb-kBti z<|YF0&8ssFi>rzS#0OFEVnGC?K&2I#_3@+;nssQDS&f^6c7?RLBovvq+ztrVIXtyI zy!c)_?W%$q1lUR7+P`%_K1JqdU+vze9-oyQ43+4$5t2c!cx}ABF@uF*xmu9D|B~T< z$-NdeQ|?W)Z0PTX_Ib^!yNXTB)J=*_YxTrHp;QwbudXCN3sD*f#cijn*(=+Q2{hGYDPd>Homzx{Q;B`7`CohfU!}~1-U7+*+!(ZN|KD+E z|Ig#&>B+zI87C{DzCREW`6?1?8zKX+leXA-f zqRK|-x>WeuMO^S{*a3T$(!ZXj@o+clEA9$+a6}Uxdr*~Vb*blO*&H4 z&PKnVE_D{yDHl)kddhf_0 zm7*qL`bd|#Ke{b%^R)ZF%O|?K(SK|IKb{^=+T;K9@c5zs-^VjN{yUG?`2khn*M4|f zuou>aCbO?LO|g1B?GClGfeFwS(!=wn?M1m6!;VGN6O10x+F@|)4#z2LcrWitqx6X1 zLPY3Sw5yj_jZo~!Z}Tm-A|>ol}2s2*V0@*I1DRDF{wZAr~AWE-`A5UNFf z55taw$BexVwYO{i9)=rzhA7(k@NVS26bpmo=Uh}I1Me3kF^`mmV4B8*E0Ne!RMZ&iB;|w_KRCFXkd&>lKn78U@#aF`c zdv_!Ce?WmJ<1ScLbN9IPP_1sh)oK7$@)@+vE$}+Sv_-cMxRkzGk@m#4I8+pAKo_YTM-$O^UY1vCpfefin_ z2{BRZKnz~>v*8C10Kb3VSXi}-z=KtI$!YR6O=%v3*z?)!DM?lKe1PxNBj)&NzU!si zht?SOu_P=)3VOO$)S`)U`zkiPm;hh4J)AYrut%vtwE9*Y;CLM1` z7YBDu+U?yj0l-4=%x&8BHc{F`cL-5bEJ2<6FLzo|s>?BlZuFEfIXQXqxKoIG+ho&h zXt(owj8*4$xI;E(qkD3K*65}c@!W5P?_N94zeDY)$1|3`vO)bWL!n^Q#bT}ChrY7zYJUW~_-i=Fv_mE&eRM>R6d(jUVqq(ZRX2wLveTo7eVluq1P@&w&4LQ+)4c8nDIxpG?Qy z_22R12mk**p7-y!Cu0v~fHXHG_RkhN9k!bkeoxbe;faH*?p;RjTcq!BYO<}U_O?}x z)7i=z3>G+Tr{r$je%f_zs~T^>BM+gTf0bvz{@cS<@5TO4k50Pw|M>91{@=&bKLNG7 z!H04H>!Ww0mEFts0ahTG zqDvUubU?a?YPDq7-X?GNY4lIp2a&GH)^TS83mZqB?aX-SZ9aNdgPY!ZMpe9e={4fc z)Xx2NAgG@(Tj8h5m zg)Fm?J3`nCYCJOg8$XTSSOG*|Qwf-UvDdqkV($Rikt;eqK9Oa%t!hu(3jI6k=QNts zc_&^N=7zx<6xTk$ygMNcO@ytJ6gEEP)RnG$YlI zYF&75xEI)i&jt>!Hs{>-Zi?Er<-1+Nsdo@=hgf||u8-&6ndaVAE0<0fRok96*D8Gy zJsy7#YU6Bu(p<1~3wz%8?sD4@)#(qV+p)qJe>3S2L0wanGQ+{|aa-qt}}DOK!HY*)U`WwW7TANL?O*T)F)PZAw4^sUZl zp$8Ll`;(oc_SusjM*HB&?E_A?#Fj0A{IWOaxW}wL&oBED1#g#tyrrOT1{Q`bAoDhK8-`_Z~HFZt}YIqz3m;rsz@-{e7MMYo#XyKMo7p)3~C?>X3wb8{*IPp$lF)cA@^__p$4 zk(NuAM~wpy!?w@i?ZmsO&tSR!104s$!DjElz}%$^VJL3(BdXo#=Go>=nB(Kys?^Lp z8bs_4TfH&1UaL8vj5`?44OpI8n{G9!HyWQ@o8NAba`{&GIa2Lzw>=@=+;|Tme*_C| zvf?~|UMts(pt!?Z>sg*VPBqvf;3r$cw~ccA<7;cM#-X-%THc^44Jy)IlC$P7|1Q^mMm#LT;nkw=cH0z*bSW@JPZQ zuLHBizhR;}wZ2u6)=jqv;*yF7rUgxuZgcMH$QkXzGxuF^t6Dm12l$5Ex#o%BU5@?2 zn`@1IM%#~HH4g#bO1p*~91wa3H~%7s`f)cC`5uQ~oMgX2dnc`j*T12)L7)Y=!5?m* zb?m7ZD&8ie3VEjy^WSta>_6}^>wnP-(u^&0F5n}SvGx3)!*>49^!Vr@|MOm+eR4{b z0+Hig21W(UEk*N^r3uTIq@eKyU4o2)eRG{rbx5CFL7L(n)Mgcm<;yRKfRrkB3AiQH zdr$Ku*e5wG&1FZA3cMl+65}%c6z$+l| zMeuo~2kX!OU$9SpqJr^Kk{91RmqBzXW1c_|6@vc)u?ixV038{4!G8&U_{Xc~uh0Gz zrqT5O*Uz7R^ZoNEOFl{&Th{+iy7_;T$K!|f|9g4t?dU=9m!GRVfJt;5jsIC)>Cdud zBv*L+J>E)3lO>ESaEP#>cY8BU|F=}@6uw%dRE6@_k_tE=fBD&L4yOgKoNK@>1!(xt z1K*{LI#M(v(`dT25;c5?;9JWGg73`zj`o_`GnOw?AYbDSETTQkAlTn0X2T$PhQB{2 z|J`$Ob>p7Mzap0L%7WmU{2jFCjyvbC$u~9{rvH+$V0b|!xel&F{g{8<{~CYcui(0x z!INvk=h|M8Yx4H}hqvVVkG`vgAbSnZzr$hAMN3|03R1A=v0W3XgdVW2ce@l=ix{?C zHjMOJ6XcXhWbo&IY6hD(0U>Y%&n={1>io@y+-F{&5VPQknaCgbmzN4J?; z(G^SUz9@%OCFSgITDQf0f$)~ZO~XxqGs)FaTo3_U79oZ}7ts(N|ebr;^6Y`szQRN1!Ry=&~-a(~wF zAb8QVKs91TtD6i~oA&Yas8#Y{bh$^dUbuTl4U9BC&S%oywoacITp572L9t$=a6@q8l5|%QmHxkXh9Dg|;(SnUGCnLPTy)FK% zOclenjMvY%bgb?nzZ-grw?y1v*T0;-&CIVQ2zCoqsPGz;*53;BHR zWnawP39qg*XoTiGUuV3Oq?FnhNvNWP&UvYf>#(8)4*t|$+K69VU(&g3F=G=( z8Wm_vox3%tGY&TNc>#KQXD%Z0NKcPeZGzi9=mv2Ay#SSQKb(xm8s~3s-kcHxO$;D0 zfHf(z2oxznV~Fg5iA`?tR@@H14Lk%fX8||IyDBMbgn)80nX7F?uDXnvT%Jw6p|e)$ zPY8LMvV8d?g0x6CSDJgq0Jb3C;$_@g?YtFZZ(_qFdYMKMebd=am)g|coot%hcv`~= zAvbY-h7StfUV-*W-y28O!Q-CW{0^bu@wJ@*z`zary)EB5ZF%r28SMf)cytD>!-Q-tH z!a+n{DBbjs$Gp^D69zhr^frsGf8LGBR?5jQM5NK>G#rl(0$uXYljxX)f7Lay6$FAI z_+2SWP-QW)s5fnsar9WXN!3qG*ryO+zTk}D3o>83H102m?yY3aOF__^bcs*~@$E=u zMZ*<+tEKn0=u2HF|~q#r-?T42?!Uu%Hb;dqt|Tj@@cLAM@z1WFn|L)~!q{5o3{8vB|5H=Y$=mD_C;A@&JZO&{DkXje88G=@;?;c!lfl0ma zy#dO4wP$AiaRZ=$UrT02jvb(J3rrjC!5w^X2Or$Q2Y2ufb_dCWKX{-1Ao&k)2p^t@ X=i&K}dH#O@00960M~_E|0JZ=CleCpq literal 0 HcmV?d00001 diff --git a/helm/atoma-node/templates/atoma-node-deployment.yaml b/helm/atoma-node/templates/atoma-node-deployment.yaml index 3470ad03..1187392e 100644 --- a/helm/atoma-node/templates/atoma-node-deployment.yaml +++ b/helm/atoma-node/templates/atoma-node-deployment.yaml @@ -48,8 +48,7 @@ spec: configMap: name: {{ .Release.Name }}-config - name: data - persistentVolumeClaim: - claimName: {{ .Release.Name }}-atoma-node-data + emptyDir: {} - name: logs emptyDir: {} - name: sui-config diff --git a/helm/atoma-node/templates/pvc.yaml b/helm/atoma-node/templates/pvc.yaml new file mode 100644 index 00000000..d19c0b7a --- /dev/null +++ b/helm/atoma-node/templates/pvc.yaml @@ -0,0 +1,17 @@ +{{- if .Values.atomaNode.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-atoma-node-data + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ .Release.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + accessModes: + - {{ .Values.atomaNode.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.atomaNode.persistence.size }} + storageClassName: {{ .Values.atomaNode.persistence.storageClass }} +{{- end }} diff --git a/helm/atoma-node/values-dev.yaml b/helm/atoma-node/values-dev.yaml index 3b254be2..2932e0b8 100644 --- a/helm/atoma-node/values-dev.yaml +++ b/helm/atoma-node/values-dev.yaml @@ -18,12 +18,10 @@ atomaNode: memory: "8Gi" cpu: "4" service: - main: - port: 3000 - daemon: - port: 3001 - p2p: - port: 4001 + port: 3000 + daemonPort: 3001 + p2pPort: 4001 + port: 3000 ingress: enabled: true className: "nginx" @@ -48,7 +46,7 @@ atomaNode: service: sglang persistence: enabled: true - storageClass: "standard" + storageClass: "gp2" size: 10Gi accessMode: ReadWriteOnce config: @@ -78,7 +76,7 @@ vllm: cpu: "8" nvidia.com/gpu: 1 service: - port: 8000 + port: 3000 extraEnv: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://otel-collector:4317 @@ -105,6 +103,7 @@ sglang: nvidia.com/gpu: 1 # Reduced for development service: port: 3000 + type: ClusterIP extraEnv: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://otel-collector:4317 @@ -124,7 +123,7 @@ postgresql: persistence: size: 5Gi service: - port: 5432 + port: 3000 # Monitoring stack settings prometheus: @@ -161,7 +160,6 @@ grafana: - grafana-dev.atoma.network service: type: LoadBalancer - port: 3000 targetPort: 3000 annotations: metallb.universe.tf/address-pool: grafana-pool @@ -173,7 +171,7 @@ loki: config: | auth_enabled: false server: - http_listen_port: 3100 + port: 3000 common: path_prefix: /var/loki replication_factor: 1 From b612ede9a15ac093c59e2b8b3d24211bc72d7df9 Mon Sep 17 00:00:00 2001 From: maschad Date: Mon, 9 Jun 2025 16:16:27 +0000 Subject: [PATCH 15/15] feat: Configure Kubernetes deployment with file-based config - Add files/config.toml for centralized configuration management - Update ConfigMap template to support file-based config loading - Configure HF_TOKEN environment variables for vllm and sglang - Add service definitions for inference endpoints - Replace sensitive tokens with environment variable placeholders - Fix CrashLoopBackOff issues for vllm pods with proper authentication --- helm/atoma-node/files/config.toml | 86 +++++++++++++++++++ helm/atoma-node/templates/configmap.yaml | 6 +- helm/atoma-node/templates/services.yaml | 39 +++++++++ .../templates/sglang-deployment.yaml | 43 ++++------ .../atoma-node/templates/vllm-deployment.yaml | 22 +++-- helm/atoma-node/values-dev.yaml | 74 +++++++++++----- 6 files changed, 215 insertions(+), 55 deletions(-) create mode 100644 helm/atoma-node/files/config.toml create mode 100644 helm/atoma-node/templates/services.yaml diff --git a/helm/atoma-node/files/config.toml b/helm/atoma-node/files/config.toml new file mode 100644 index 00000000..c2312a62 --- /dev/null +++ b/helm/atoma-node/files/config.toml @@ -0,0 +1,86 @@ +[atoma_sui] +atoma_db = "0x02920289f426dd1f3c2572d613f7dc92be95041720864a73d44d65585530efc5" +atoma_package_id = "0x8903298ba49a8e83d438e014b2cfd18404324f3a0274b9507b520d5745b85208" +cursor_path = "./cursor.toml" +http_rpc_node_addr = "https://fullnode.testnet.sui.io:443" +limit = 100 +max_concurrent_requests = 10 +request_timeout = { secs = 300, nanos = 0 } +sui_config_path = "/root/.sui/sui_config/client.yaml" +sui_keystore_path = "/root/.sui/sui_config/sui.keystore" +usdc_package_id = "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29" + +[node] +address = "0.0.0.0" +port = 3000 + +[daemon] +address = "0.0.0.0" +port = 3001 + +[p2p] +address = "0.0.0.0" +port = 4001 + +[database] +url = "postgresql://atoma_node_dev:dev_password@atoma-node-dev-postgresql:5432/atoma_node_dev" + +[logging] +level = "debug" + +[inference_services] +vllm_endpoint = "http://atoma-node-dev-vllm:8000" +sglang_endpoint = "http://atoma-node-dev-sglang:30000" + +[atoma_state] +database_url = "postgresql://atoma_node_dev:dev_password@atoma-node-dev-postgresql:5432/atoma_node_dev" + +[atoma_state.metrics_collection] +metrics_url = "http://prometheus:9090" +models = [ + [ + "Chat Completions", + "Llama-3.2-3B-Instruct", + ], +] +top_k = 10 + +[atoma_service] +chat_completions_service_urls = { "mistralai/Mistral-Nemo-Instruct-2407" = "http://atoma-node-dev-vllm-0:8000" } +service_bind_address = "0.0.0.0:8080" +password = "password" +open_router_models_file = "/app/open_router.json" +models = [ "Infermatic/Llama-3.3-70B-Instruct-FP8-Dynamic", "mistralai/Mistral-Nemo-Instruct-2407", "deepseek-ai/DeepSeek-V3-0324" ] +revisions = ["main", "main", "main"] +hf_token = "${HF_TOKEN}" +modalities = [["Chat Completions"], ["Chat Completions"], ["Chat Completions"]] +heartbeat_url = "https://hc-ping.com/6981b450-3453-40f9-8b53-4f95a1f30e30" +sentry_dsn = "https://bf63999f90e261523847f4474f4d510b@o4509203694419968.ingest.us.sentry.io/4509203836370944" +environment = "development" + +[atoma_proxy_service] +service_bind_address = "0.0.0.0:8081" +grafana_url = "http://213.130.147.75:3000" +grafana_api_token = "${GRAFANA_API_TOKEN}" +grafana_dashboard_tag = "proxy" +grafana_stats_tag = "stats" + +[atoma_auth] +secret_key = "atoma_auth123" +access_token_lifetime = 1 +refresh_token_lifetime = 1 +google_client_id = "135471414073-41r9t89rejgfr6bc9aptjpm75o4oedk2.apps.googleusercontent.com" + +[atoma_p2p] +heartbeat_interval = { secs = 30, nanos = 0 } +idle_connection_timeout = { secs = 60, nanos = 0 } +listen_addrs = [ + "/ip4/0.0.0.0/tcp/8083", + "/ip4/0.0.0.0/udp/8083/quic-v1", +] +bootstrap_node_addrs = [ + "/ip4/213.130.147.75/tcp/8083", + "/ip4/213.130.147.75/udp/8083/quic-v1", +] +metrics_endpoints = {} +local_key = "/app/data/local_key" diff --git a/helm/atoma-node/templates/configmap.yaml b/helm/atoma-node/templates/configmap.yaml index 260420a9..fc715914 100644 --- a/helm/atoma-node/templates/configmap.yaml +++ b/helm/atoma-node/templates/configmap.yaml @@ -8,6 +8,10 @@ metadata: app.kubernetes.io/instance: {{ .Release.Name }} data: config.toml: |- +{{- if .Values.atomaNode.config.content }} +{{ .Values.atomaNode.config.content | indent 4 }} +{{- else }} {{ .Files.Get "files/config.toml" | indent 4 }} +{{- end }} environment: {{ .Values.atomaNode.config.environment | quote }} - log_level: {{ .Values.atomaNode.config.logLevel | quote }} \ No newline at end of file + log_level: {{ .Values.atomaNode.config.logLevel | quote }} diff --git a/helm/atoma-node/templates/services.yaml b/helm/atoma-node/templates/services.yaml new file mode 100644 index 00000000..285a684f --- /dev/null +++ b/helm/atoma-node/templates/services.yaml @@ -0,0 +1,39 @@ +{{- if .Values.vllm.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-vllm + labels: + app: vllm + release: {{ .Release.Name }} +spec: + type: ClusterIP + ports: + - port: {{ .Values.vllm.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: vllm + release: {{ .Release.Name }} +--- +{{- end }} +{{- if .Values.sglang.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-sglang + labels: + app: sglang + release: {{ .Release.Name }} +spec: + type: {{ .Values.sglang.service.type | default "ClusterIP" }} + ports: + - port: {{ .Values.sglang.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: sglang + release: {{ .Release.Name }} +{{- end }} diff --git a/helm/atoma-node/templates/sglang-deployment.yaml b/helm/atoma-node/templates/sglang-deployment.yaml index 4f1443f7..d8bd0e29 100644 --- a/helm/atoma-node/templates/sglang-deployment.yaml +++ b/helm/atoma-node/templates/sglang-deployment.yaml @@ -28,31 +28,24 @@ spec: resources: {{- toYaml .Values.sglang.resources | nindent 12 }} env: - - name: HF_TOKEN - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-sglang-secrets - key: hf-token - command: - - python3 - - -m - - sglang.launch_server + - name: CUDA_VISIBLE_DEVICES + value: "0" + {{- with .Values.sglang.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.sglang.model.args }} args: - - --model-path - - {{ .Values.sglang.modelPath }} - - --tp - - "8" - - --enable-dp-attention - - --dp - - "8" - - --trust-remote-code - - --max-running-requests - - "128" - - --enable-metrics - - --host + {{- toYaml .Values.sglang.model.args | nindent 12 }} + {{- else }} + command: ["python", "-m", "sglang.launch_server"] + args: + - "--model-path" + - "{{ .Values.sglang.model.name }}" + - "--host" - "0.0.0.0" - - --port - - {{ .Values.sglang.service.port | quote }} + - "--port" + - "{{ .Values.sglang.service.port }}" + {{- end }} --- apiVersion: v1 kind: Service @@ -62,7 +55,7 @@ metadata: app: sglang release: {{ .Release.Name }} spec: - type: {{ .Values.sglang.service.type }} + type: {{ .Values.sglang.service.type | default "ClusterIP" }} ports: - port: {{ .Values.sglang.service.port }} targetPort: http @@ -71,4 +64,4 @@ spec: selector: app: sglang release: {{ .Release.Name }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/helm/atoma-node/templates/vllm-deployment.yaml b/helm/atoma-node/templates/vllm-deployment.yaml index 75fc9cb1..0bdcecc1 100644 --- a/helm/atoma-node/templates/vllm-deployment.yaml +++ b/helm/atoma-node/templates/vllm-deployment.yaml @@ -6,20 +6,20 @@ metadata: name: {{ $.Release.Name }}-vllm-{{ $i }} labels: app: vllm - instance: {{ $i }} + instance: "{{ $i }}" release: {{ $.Release.Name }} spec: replicas: 1 selector: matchLabels: app: vllm - instance: {{ $i }} + instance: "{{ $i }}" release: {{ $.Release.Name }} template: metadata: labels: app: vllm - instance: {{ $i }} + instance: "{{ $i }}" release: {{ $.Release.Name }} spec: containers: @@ -40,7 +40,15 @@ spec: value: "1" - name: CUDA_VISIBLE_DEVICES value: "{{ $i }}" + {{- with $.Values.vllm.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if $.Values.vllm.model.args }} + args: + {{- toYaml $.Values.vllm.model.args | nindent 12 }} + {{- else }} command: {{ $.Values.vllm.command | default (list "--model" $.Values.vllm.model "--max-model-len" $.Values.vllm.maxModelLen) }} + {{- end }} --- apiVersion: v1 kind: Service @@ -48,10 +56,10 @@ metadata: name: {{ $.Release.Name }}-vllm-{{ $i }} labels: app: vllm - instance: {{ $i }} + instance: "{{ $i }}" release: {{ $.Release.Name }} spec: - type: {{ $.Values.vllm.service.type }} + type: {{ $.Values.vllm.service.type | default "ClusterIP" }} ports: - port: {{ $.Values.vllm.service.port }} targetPort: http @@ -59,7 +67,7 @@ spec: name: http selector: app: vllm - instance: {{ $i }} + instance: "{{ $i }}" release: {{ $.Release.Name }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/helm/atoma-node/values-dev.yaml b/helm/atoma-node/values-dev.yaml index 2932e0b8..45066b34 100644 --- a/helm/atoma-node/values-dev.yaml +++ b/helm/atoma-node/values-dev.yaml @@ -21,7 +21,6 @@ atomaNode: port: 3000 daemonPort: 3001 p2pPort: 4001 - port: 3000 ingress: enabled: true className: "nginx" @@ -52,19 +51,20 @@ atomaNode: config: environment: "development" logLevel: "debug" + # Atoma node configuration will be read from files/config.toml extraEnv: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://otel-collector:4317 - name: RUST_LOG value: "info" -# VLLM settings +# VLLM settings for Mistral-Nemo vllm: enabled: true - replicas: 1 # Reduced for development + replicas: 1 image: repository: vllm/vllm-openai - tag: v0.8.1 + tag: v0.6.2 pullPolicy: Always resources: requests: @@ -76,7 +76,24 @@ vllm: cpu: "8" nvidia.com/gpu: 1 service: - port: 3000 + port: 8000 + model: + name: "mistralai/Mistral-Nemo-Instruct-2407" + args: + - "--model" + - "mistralai/Mistral-Nemo-Instruct-2407" + - "--served-model-name" + - "mistral-nemo" + - "--host" + - "0.0.0.0" + - "--port" + - "8000" + - "--tensor-parallel-size" + - "1" + - "--gpu-memory-utilization" + - "0.9" + - "--max-model-len" + - "8192" extraEnv: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://otel-collector:4317 @@ -84,26 +101,41 @@ vllm: value: vllm - name: OTEL_LOGS_EXPORTER value: otlp + - name: HF_TOKEN + value: "${HF_TOKEN}" -# SGLang settings +# SGLang settings for Mistral-Nemo sglang: enabled: true image: repository: lmsysorg/sglang - tag: latest + tag: v0.3.5.post1 pullPolicy: Always resources: requests: memory: "32Gi" cpu: "8" - nvidia.com/gpu: 1 # Reduced for development + nvidia.com/gpu: 1 limits: memory: "64Gi" cpu: "16" - nvidia.com/gpu: 1 # Reduced for development + nvidia.com/gpu: 1 service: - port: 3000 + port: 30000 type: ClusterIP + model: + name: "mistralai/Mistral-Nemo-Instruct-2407" + args: + - "--model-path" + - "mistralai/Mistral-Nemo-Instruct-2407" + - "--host" + - "0.0.0.0" + - "--port" + - "30000" + - "--tp-size" + - "1" + - "--mem-fraction-static" + - "0.9" extraEnv: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://otel-collector:4317 @@ -111,6 +143,8 @@ sglang: value: sglang - name: OTEL_LOGS_EXPORTER value: otlp + - name: HF_TOKEN + value: "${HF_TOKEN}" # PostgreSQL settings postgresql: @@ -118,12 +152,13 @@ postgresql: auth: database: atoma_node_dev username: atoma_node_dev - password: "dev_password" # Change this in production + password: "dev_password" primary: persistence: size: 5Gi service: - port: 3000 + ports: + postgresql: 5432 # Monitoring stack settings prometheus: @@ -148,18 +183,15 @@ grafana: enabled: true persistence: size: 5Gi - admin: - existingSecret: "grafana-admin" - userKey: admin-user - passwordKey: admin-password - security: - adminPassword: "admin123" # Change this in production + adminUser: admin + adminPassword: admin123 ingress: enabled: true hosts: - grafana-dev.atoma.network service: type: LoadBalancer + port: 3000 targetPort: 3000 annotations: metallb.universe.tf/address-pool: grafana-pool @@ -167,11 +199,10 @@ grafana: loki: enabled: true deploymentMode: SingleBinary - auth_enabled: false - config: | + loki: auth_enabled: false server: - port: 3000 + http_listen_port: 3100 common: path_prefix: /var/loki replication_factor: 1 @@ -198,7 +229,6 @@ loki: persistence: enabled: true size: 10Gi - storageClass: local-path write: replicas: 0 read: