From bba35341fb70d9532ee97693d761a6d1a219f60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Henrique?= Date: Tue, 3 Oct 2023 15:31:33 -0300 Subject: [PATCH] melhorando testes e adicionando actix --- ASP.NET/Services/ImageService.cs | 10 +- ActixAPI/.dockerignore | 2 + ActixAPI/Cargo.lock | 226 +++++++++++++++++++++++++++++++ ActixAPI/Cargo.toml | 4 +- ActixAPI/Dockerfile | 39 ++++-- ActixAPI/src/main.rs | 68 ++++++++-- FlaskAPI/controllers/image.py | 6 +- FlaskAPI/services/image.py | 37 +++-- FlaskAPI/temp_image.jpeg | Bin 44871 -> 0 bytes docker-compose.yml | 15 +- scripts/common.py | 27 ++++ scripts/graph.py | 85 +++++++++--- scripts/testes.py | 102 +++++++------- tcc-express | 1 + 14 files changed, 496 insertions(+), 126 deletions(-) create mode 100644 ActixAPI/.dockerignore delete mode 100644 FlaskAPI/temp_image.jpeg create mode 100644 scripts/common.py create mode 160000 tcc-express diff --git a/ASP.NET/Services/ImageService.cs b/ASP.NET/Services/ImageService.cs index dfa6ce3..e66e2b5 100644 --- a/ASP.NET/Services/ImageService.cs +++ b/ASP.NET/Services/ImageService.cs @@ -9,11 +9,13 @@ namespace TCC.Services public MagickImage BoxBlurImage(Stream imageStream, int radius) { var image = new MagickImage(imageStream); - var blurredImage = new MagickImage(image); + image.GaussianBlur(radius, radius); + return image; + //var blurredImage = new MagickImage(image); - blurredImage = BoxBlurImageSeparable(image, blurredImage, radius, 0); - blurredImage = BoxBlurImageSeparable(blurredImage, blurredImage, 0, radius); - return blurredImage; + //blurredImage = BoxBlurImageSeparable(image, blurredImage, radius, 0); + //blurredImage = BoxBlurImageSeparable(blurredImage, blurredImage, 0, radius); + //return blurredImage; } private MagickImage BoxBlurImageSeparable(MagickImage image, MagickImage blurredImage, int radiusX, int radiusY) diff --git a/ActixAPI/.dockerignore b/ActixAPI/.dockerignore new file mode 100644 index 0000000..8487999 --- /dev/null +++ b/ActixAPI/.dockerignore @@ -0,0 +1,2 @@ +Dockerfile +target/ \ No newline at end of file diff --git a/ActixAPI/Cargo.lock b/ActixAPI/Cargo.lock index ab09c63..3784675 100644 --- a/ActixAPI/Cargo.lock +++ b/ActixAPI/Cargo.lock @@ -8,6 +8,8 @@ version = "0.1.0" dependencies = [ "actix-files", "actix-web", + "magick_rust", + "qstring", ] [[package]] @@ -296,6 +298,29 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags 2.4.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.32", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -363,12 +388,32 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -443,6 +488,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -452,6 +503,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.27" @@ -534,6 +606,12 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "h2" version = "0.3.21" @@ -559,6 +637,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -629,12 +716,40 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + [[package]] name = "local-channel" version = "0.1.3" @@ -669,6 +784,17 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "magick_rust" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b69250bcd5d024852a1a75c567d3b5d881871a55b741018741632a921bf8" +dependencies = [ + "bindgen", + "libc", + "pkg-config", +] + [[package]] name = "memchr" version = "2.6.3" @@ -691,6 +817,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -712,6 +844,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "object" version = "0.32.1" @@ -756,6 +898,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -786,6 +934,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.32", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -795,6 +953,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + [[package]] name = "quote" version = "1.0.33" @@ -878,6 +1045,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -887,6 +1060,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.15" @@ -959,6 +1145,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1169,6 +1361,40 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/ActixAPI/Cargo.toml b/ActixAPI/Cargo.toml index 48d2712..776380e 100644 --- a/ActixAPI/Cargo.toml +++ b/ActixAPI/Cargo.toml @@ -7,4 +7,6 @@ edition = "2021" [dependencies] actix-web = "4" -actix-files = "0.6.2" \ No newline at end of file +actix-files = "0.6.2" +magick_rust = "0.19.1" +qstring = "0.7.2" diff --git a/ActixAPI/Dockerfile b/ActixAPI/Dockerfile index dce9b80..aa2f7da 100644 --- a/ActixAPI/Dockerfile +++ b/ActixAPI/Dockerfile @@ -1,25 +1,34 @@ -FROM rust:slim-bullseye AS build-env +FROM rust:latest + +ENV MAGICK_VERSION 7.1 + +RUN apt-get update \ + && apt-get -y install wget curl build-essential clang pkg-config libjpeg-turbo-progs libpng-dev + +RUN apt update && apt install curl -y \ + && curl https://imagemagick.org/archive/ImageMagick.tar.gz | tar xz \ + && cd ImageMagick-${MAGICK_VERSION}* \ + && ./configure --with-magick-plus-plus=no --with-perl=no \ + && make \ + && make install \ + && cd .. \ + && rm -r ImageMagick-${MAGICK_VERSION}* WORKDIR /app +RUN wget https://files.ivanch.me/api/public/dl/iFuXSNhw/small-image.png && \ + wget https://files.ivanch.me/api/public/dl/81Bkht5C/big-image.png && \ + wget https://files.ivanch.me/api/public/dl/nAndfAjK/video.mp4 + COPY . . -RUN apt update && apt install wget -y && \ - wget https://files.ivanch.me/api/public/dl/iFuXSNhw/small-image.png && \ - wget https://files.ivanch.me/api/public/dl/81Bkht5C/big-image.png && \ - wget https://files.ivanch.me/api/public/dl/nAndfAjK/video.mp4 && \ +RUN cargo build --release && \ + cp ./target/release/ActixAPI . && \ mv small-image.png ./static && \ mv big-image.png ./static && \ - mv video.mp4 ./static + mv video.mp4 ./static && \ + ldconfig /usr/local/lib -RUN cargo build --release - -FROM debian:bullseye-slim - -WORKDIR /app - -COPY --from=build-env /app/target/release . - -COPY --from=build-env /app/static ./static +ENV LD_LIBRARY_PATH=/usr/local/lib ENTRYPOINT ["./ActixAPI"] diff --git a/ActixAPI/src/main.rs b/ActixAPI/src/main.rs index 09c170f..878169d 100644 --- a/ActixAPI/src/main.rs +++ b/ActixAPI/src/main.rs @@ -1,21 +1,13 @@ +use qstring::QString; use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest, Result}; use actix_files::NamedFile; -use std::path::PathBuf; +use magick_rust::MagickWand; #[get("/status/ok")] async fn hello() -> impl Responder { HttpResponse::Ok().body("{\"status\": 200}") } -#[post("/echo")] -async fn echo(req_body: String) -> impl Responder { - HttpResponse::Ok().body(req_body) -} - -async fn manual_hello() -> impl Responder { - HttpResponse::Ok().body("Hey there!") -} - async fn static_serve(req: HttpRequest) -> Result { let path: &str = req.path(); let real_path = &path[1..]; @@ -23,6 +15,53 @@ async fn static_serve(req: HttpRequest) -> Result { Ok(NamedFile::open(real_path)?) } +#[get("/image/load-small-image")] +async fn load_small_image() -> Result { + let real_path = "static/small-image.png"; + + Ok(NamedFile::open(real_path)?) +} + +#[get("/image/load-big-image")] +async fn load_big_image() -> Result { + let real_path = "static/big-image.png"; + + Ok(NamedFile::open(real_path)?) +} + +#[post("/image/save-big-image")] +async fn save_big_image(image_data: web::Bytes) -> Result { + // Write bytes to file + std::fs::write("image.png", &image_data).unwrap(); + + // Return the blurred image bytes + Ok(HttpResponse::Ok() + .content_type("application/json") + .body("{\"status\": 200}")) +} + +#[post("/image/blur")] +async fn blur_image(image_data: web::Bytes, req: HttpRequest) -> Result { + // Load the image from the incoming bytes + let mut wand = MagickWand::new(); + wand.read_image_blob(&image_data).unwrap(); + + let query_str = req.query_string(); + let qs = QString::from(query_str); + let radius = qs.get("radius").unwrap_or("5").parse::().unwrap_or(5.0); + + // Blur the image + wand.blur_image(radius, radius).unwrap(); + + // Convert the image back to bytes + let blurred_image_bytes = wand.write_image_blob("png").unwrap(); + + // Return the blurred image bytes + Ok(HttpResponse::Ok() + .content_type("image/png") + .body(blurred_image_bytes)) +} + #[actix_web::main] async fn main() -> std::io::Result<()> { println!("Hello, world!"); @@ -31,10 +70,13 @@ async fn main() -> std::io::Result<()> { App::new() .route("/static/{filename:.*}", web::get().to(static_serve)) .service(hello) - .service(echo) - .route("/hey", web::get().to(manual_hello)) + .service(load_small_image) + .service(load_big_image) + .service(save_big_image) + .service(blur_image) + .app_data(web::PayloadConfig::new(1024 * 1024 * 1024)) }) - .bind(("0.0.0.0", 9090))? + .bind(("0.0.0.0", 5000))? .run() .await } \ No newline at end of file diff --git a/FlaskAPI/controllers/image.py b/FlaskAPI/controllers/image.py index aac4dbc..6fb10b9 100644 --- a/FlaskAPI/controllers/image.py +++ b/FlaskAPI/controllers/image.py @@ -11,10 +11,10 @@ def blur_image(): image = request.get_data() if radius and image: - return send_file(image_service.box_blur_image(image, radius), - mimetype='image/jpeg', + return send_file(io.BytesIO(image_service.box_blur_image(image, radius)), + mimetype='image/png', as_attachment=True, - download_name='blurred_image.jpeg') + download_name='blurred_image.png') return "Bad request", 400 diff --git a/FlaskAPI/services/image.py b/FlaskAPI/services/image.py index e4caeac..2ced7c5 100644 --- a/FlaskAPI/services/image.py +++ b/FlaskAPI/services/image.py @@ -1,29 +1,33 @@ -from wand.image import Image +from wand.image import Image, Color def box_blur_image_separable(image, radius_x, radius_y): - blurred_image = image.clone() width, height = image.width, image.height + kernel_x_size = 2 * radius_x + 1 + kernel_y_size = 2 * radius_y + 1 + kernel_area = kernel_x_size * kernel_y_size + + blurred_image = image.clone() for y in range(height): for x in range(width): - blurred_image[x, y] = image[x, y] - r_total, g_total, b_total = 0, 0, 0 - pixel_count = 0 + for offset_y in range(-radius_y, radius_y + 1): for offset_x in range(-radius_x, radius_x + 1): new_x = x + offset_x new_y = y + offset_y if 0 <= new_x < width and 0 <= new_y < height: - r_total += image[new_x, new_y].red_int8 - g_total += image[new_x, new_y].green_int8 - b_total += image[new_x, new_y].blue_int8 - pixel_count += 1 + pixel = image[new_x, new_y] + r_total += pixel.red_int8 + g_total += pixel.green_int8 + b_total += pixel.blue_int8 - blurred_image[x, y].red_int8 = int(r_total / pixel_count) - blurred_image[x, y].green_int8 = int(g_total / pixel_count) - blurred_image[x, y].blue_int8 = int(b_total / pixel_count) + r_avg = int(r_total / kernel_area) + g_avg = int(g_total / kernel_area) + b_avg = int(b_total / kernel_area) + + blurred_image[x, y] = Color(f'rgb({r_avg},{g_avg},{b_avg})') return blurred_image @@ -33,9 +37,12 @@ class ImageService: def box_blur_image(self, img, radius): with Image(blob=img) as image: - blurred_image = box_blur_image_separable(image, radius, 0) - blurred_image = box_blur_image_separable(blurred_image, 0, radius) - return blurred_image.make_blob() + image.gaussian_blur(radius, radius) + return image.make_blob() + + # blurred_image = box_blur_image_separable(image, radius, 0) + # blurred_image = box_blur_image_separable(blurred_image, 0, radius) + # return blurred_image.make_blob() def get_simple_image(self): with open("./static/small-image.png", "rb") as file: diff --git a/FlaskAPI/temp_image.jpeg b/FlaskAPI/temp_image.jpeg deleted file mode 100644 index 25783da8f0a2940f339282693325054aad791752..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44871 zcmeFYcT`kMx9Gdbk_9A5XcZIzQ31)J6_un&5}F`15+vswTSP>O0s;z3gMdiRAhA&- zOHPt=rpck9(_Ogt+242WIq#19-u>&2@g_}Etg*V*oK>^ttgc@b@i%b>IIFI#rVNmf z0Dvm^0}y9r9^Ut{eFgv;8h{W00A~P75@vu5ERlde0LgiP;!ha>^hvn>DVvg9{retL z03c=g*ZpC(0QukdgLMLp;4k=;)0+PNOZ652sKEcoZ;FYD#*+M1CLtvQ(*EPR=*^pH zB>$>0jr2ciB)ytO_8(=E)J)31?*S|OBa|d$e_x^ecfCm%{-rkw+keuVfA{U^PNzlr0(OR(Keg%o^|k&=>NkRF~_5bYxaT3&-hS&%&odJA+G%}J)04Wm*850TdC&_2fY>Gb$o;LR%0rdnG zqdG%PLwgn=B_SgtB`2c*^#$(?JZ*GxCJN?@qIW1+w4YL4ay=&&9GiZIPpRZPt4`lG zzqq;E3u+oRb`H+-m#+w16%@K9At@y-BYXGWePtC@HFe#`dipQ}Ln8}It7q0Wws!6w zo?hNQzJ4JuLtllxeiI%S|1Kdh>HUY~&l#CnU$VdEHP5NWe|DHiF z{y$mtk3s*~LmUI>$Vk8rBVz)f0B)l%K#p5ge)gjW$5K7*quw#Aie{Tm-I<8j_=@v=M^HKmh8Vx7F;#*j}%;8q41MLX0%rQYGF?&0_(aSQ8$H2(<~X) z*UKa7*-UJ43amd;YJC!|s~~|EZb~J*FJDj%j1}i-rreX#k-9wK@sb%}UsNCh+3ITr z9)$tLnGmDerzF)Y}tvB=)s?dRTdto1fCJ6v#+ZuXOQ z;I=I!LeD+V0a%&hk1rz!Mu9vm_4Sl{6L00R|P)@2ymb1 z%3qvwro|-O8>8s0W9U~_&j{uke_N_@aZJAYT=6l)-j9yvz?XS2#x5f9FbLKWa_(?| zTP8s=ZYp6|;tM4vDSJ47e04MLTTFH~YiahOp<~mY+JHv#X=L*#=iCQBIx~UpVK^J(&Ks z_tA!P4AjTT-RystWufw*geIV(1&x|Dy))PIa!O%`@wJ5W+0__w^A&@!d|H>Cz|X^@ z*|uaIC8DoO-m{VzZ+b4rxD`iq#QzfB<3^n1l~vsB^;F2~d&R>y;QGS8?f5{E!sn|` z!-!$kuxQq+u$~I}b_UH#&(Pu9s5ZscijZ%>FC@CG}mC zMPnP@jG{v?23rmSq1t7UuF3V}0(Xf^5!e9Epec5=1P;766{#DwS@vu}02;V;5pmW%&Ir^5PM5eM#;gRaOuxfrbE$BNmf3xZPeoy6^Y4(Z4FbB&As)c0<^ngKJy$kO&;;`C1qd z)(CnF-hR*0Gv3c7RBmx4S#Ud|TUmc%2IfRDp0oZ$fWs#apEgk5c7D#5dqF6z2Ur#; zX6>U(`Cxi&$){6Jd=E>YeVX$io6wQ$n7VmG{=UPvR;{<)Q&TTcNr6(0rs5;z2g4o; z*NtoS-~Qx}B$bNJi(HP$LTJe%ap!0c`7!A9@3YPk@kVy~CF*7T=MUevT)ax|#6?CS z%}*-l`b6p{JQ!6BLsQS_W(Az9-1bVyt?O&6l8k$e?0m8rV`lZVmJj2vj~qR?@XknJ zoylMPcmqzq(!knxDWa7vDbXmhV+ktCbB;R{ppbtF5tZJm;-(?Qp=M$e6ZsCEhdf2( zI}e3p&Q5W;ie8)Jk7hM(_?CyDLd|@Fmn?nQo_^RLB!tl)*BQxBW{2(ccrh!PycW_P zdH(Y1j7L!~Gz3mVV8&h9mRF?3$XKyPzoF}w4i_|f@^JY^^~W-&LxBrsuupHkcTnAz zjp7-JALZaM;pPkGf^59`(t6EGuq$ ztfDG)o2|8niR6_`)<3Nt0M7<;jja{ZlQt@cfft_`O z^%+gdhef{D*}j&|B4uF=DEbiQ^FBZJI=tEnmFHDaubQtl zFGP2wIlIQWPAq--Y{HZvarFR7?Au|a;Uk|=9W&-AGN^dX`2Mh!b*X_ZSikSCr&k+6 zsorivl7W(ub@%4v`Cj`EPbT}*Y`N&9H~hVtz1WHqHe~h-?H}&lCgls&HqonlrTfjJ zNR#n~&t^;B<<61;+ilzjg&OmkaXr|0ZOrPE(3evNV!W=+o{90kvD6&%eBlRN@*lXa zrk(X?RL_}<(&rIsa^rq*@%MX(W@5+sR1>6@) zm5iez;~U`iD2kUSo5@4b*@wUrPhj7kiK(XFR(9Y`sh;_GP?X_Vr(RiO>3?3$@`e0a zj9f=ZFmkrN@sM@jw}Gdtb+mxaw(_&}dQ#sStgJ>hL4S9I=LOi+bJjRk<2l0T6R#Mo z6h`ts_E{Cb`?S>%5Lx+~@0mI4U8!I0jkNG0Q*L%G9|ZcO0F~r>qr2Cc?yi@X3i-$~ z98FCG$bXh#mUmcmc1|$e_JR64LrX90eKs$>4h58J4LL3ahqiUg6g@guZHtvIlkM2i zK!(?#HZrgeZDqa3uGjJKmN{41ARigrpgd=GcDO74pgEu@--oJsioIIYCq=b>z?BFn zn8#g~ zJs^Ggj*|a^9}R8`<2q~N-ZX3~go&8L@>%8Ei+vK~NQUs4IcRuva?%z%?#^>?G%>+d zaWaF~8c0@*ynmwYIHM-2B#(6NJ9EZal|<|P_jwbj>TPWi*t1i{qpyX@beO)4Xf_Fy7j(>?G@j_G zqwr*S57&_)I`sF(SK=slt2()(ORppR|)O+I7kEzOg+Cid|wwZ1_ z(|N4eL>^16g?`HQ{Bfi;AGAMl@B7V6#ESb<6>GBWL35NmnlS19mET+--1xL{mag_n!Fe5@9y(RXKSkDc~KtTjf`#4bj2eXDyV*!xmi3uuLkf5gJ_FvfE+(NDJ@ z70nTW@EWmI$f!=~wVR@dax*$d?wyYea(Llhk8D;SHK?+?P)WS2y}9#iEh6CLqqyEQ zML*OM7Vy-gGI^6zQsu(Kd&Vy0xgV`RCNH3@QPddZtoM&vdX_o%@PayxoH3U>pm{ONGm7SfRD9WM$X5I?Wcun-av2CCpKd-TB&^+k{u% zrm9?#(WaLbq-vi6JYi`?=?E(xNRy%&Sq&Uz&?y88H}n^>CPd$q#^u?u`g&Xu zU+hbbwepJ#=2cjasoxp1;T4Kk1s%FH)F#86gwKY`d48-+V(VP%tOm1*M|#53MxlC_ zN75&-?jWgc_zz^{MR2C(>YfeHb5gSuVG;J{{b*4U_>4Ij!L)bat@tJE2a4`MZsh>E z@$F(YjM@4AX#9x4Ggj1$QdOPGjQuo=X$89SOJo>@<7Pp>k4k4GeHI5}4xd8a?E9c| zL4Bzdq1G7dIh%@g#XO2lemNDhnWe(fph48x0BRSJAQ22yYrwT>a+Gj#i7LQFW!AL* znt-rQ_C=Y>{GM_G8}3TOw#K}@Z0CJfe%cxiv|(jkM{WK1)PCALoVvu9J}LR_usTr>nIVkP5`X^uw>@gHz9xohw-S%KGu zBICYZ#tG5ua@_{P#fI#BUkqaG_IGp3SyisxA8}-qg9vp9kMesVNdwNkt!t3@q_=on zfTneDj`i_n9Vy&!?DlQ)8wn>2!I?8qb(%D*r7>l{C7o{iShs3o&J=yEdpg$znMDMj zDHdcOO*atXIX!!UVs5na5)GtF6sCGooT-D2+X%vCF{`}xJ2dbu?f zo-5I94sPS<01YpK&@d&MT_gT%5f0a_xW+Kfo}&O2@(z)p#=& zJ=L3VUL}0)3avihgEwK* z@3}ck@Pi8iD1}hR=$!K|aaNEVsr!tg3mu8L_je?t`qsbNb}K0SHt)T zsgq_Io||&8mWgkl-<4iM{Q`I=^RDk-YO%U@dqtE8jO4j}8-MQN$2%0_D?4vzo|yt6 z4&r9EKQ;Q`ihmM;vpglHDOpqUZu#)3B9iS-#|9bi7@%4H5((cOOS8%SMp`lhDcE}t zMZ|XRTg8cvR8@HEpMUxOxIw%1(a?e<9ePice7?f-yvfn;UCXCc-1_@`Tx+af1(n*? z>O5VXf*{lJ!4>WpQsJNZHx^uICHLQY$VbBR>|RSuQm$)+z9`=*w~%L0Vcm5~nO6`D zun64#%@zn(ZTwah1$WWNu#f6UfBWKQhbkIwm^iF&rxFZjyjm;1!X>D~NjAz}Y38$Q z&^mzPhUM8R3h$`W+h5x;y&rSnt;(SCWtPCY7noQWPPpe>^1XX_hM>YaWqb0ARv`As z%R?b=#YXA6fJ8L%=QM*Nz0bVhd<;$g^+p2W7en@_%XF5nUr@&hIZWfmcPQ`LdG0nh zk%e~~WW}PD&;)|{cd28dvOO+5O|kV>6sZxFg{CT(~1H05BH*_SSA>T4jEe@;U`8u zF1D%cCB~dv53;_GrSx8;>7Y64S%s5pvZWW<51%lEa#nw<_jq-6Ainw=E(~$U*dNB4 z7WcrS2E(@-+HaLC!xo(edE$Qb`olUKIEHYwk6*hyUgjNIKmVwD@n)>?8iVkxojG^lpnqY|K_B`s z`DO7<9u>_j5AC%bcBb}@{chp*aRey{t&dyK?E4=zdIaITRi0oE>Eo&t|_ahseL?a-xuXmULE;~m2Y5i zKpvXkVm^q@Nb%OKN>#w*+cq$-#4XmzcwWw9oOxOg9NIUEcgy_Fd^D)$m5sjdMN-y?CQ&CNC4Ok}Ah)o`{QU%9IL%jIW3LDoyi@ zY7pdWx(BN#BfXVl$zR9R_Xxc2)~Ac@N{-owcYbA(?0uEk=nm znye+Um4K!wAI2u>wN5wlo0;vVAkygw$ztB}{G(~#}NMLRK7Qzr=Nn$F(M=iDMV zC&W}?5TWLicIofi7~Bo2{9f=s9r?<^*#d3LFFZLfB%ItfR9o*}OaG$&_KIRchsm0H zb!ZRj$@%YfLsN9eYz_8WuAG&M7KYDbD!#I|{3TAD7Lz-P_(t5T5 zL)|Ox`$dpG=+KSh{ld+jtsDIfkXVHtFWIT(VgviwvIzGVn4Vdaip1($cC3n4rjx=g z`#r~WF(>o*`+k>`CMB9AtS$HkH#{HLm}TIwFioEQTFJb5vhY1eGQ8Od5l}~G5CH?l zSGC2vj$D0#42e93=P2S|$PG`NA08g-L3!VKat`HJG)e?oI|!_3?eIlqw|*nu0;@R| zUfR^u=`0WZsmoK3E#FSS-naG+FCqIBE3%E*Ir4b(D65v1Zq0M+I(c#wqEm-=`4a_U z7OfZP^0OmZ`N$y;VRBCiC4`LQ$MFZ6KWg~#gRgT~{kddi`uF`+_b0&^DreKM;kCkd zp9-097Jv#3AYl`W%8Z@E)xS>&q)7xLWHv@0*5C4OKl)Y3I{lg7bxgyh*&}ZwDHj;d z`Rmyi9lWZRBmyx_gyn<$k7K`A)LZg=UQ#q&873%Icd5XWwf9_m$o>2t!=S#Xo-DH| zeBVKV@3_90o0s{d{WIx~cx2E7OvX2-_15zlL9%SS2wfteGp%b-N(AzW!1M6#tBX+L zC`i2i8(IiF|DR|f#s32>RR0%s19bkKy8Rm=H2RxT{U_1}kp7GK0crmtejx4wg@^{E zwQQU{oZW4lT`q`UzX?byscD=7tyB3^zWk?nc917>GzKUoUwuyfegmTv^xrZw0P??N zX2hRB#s7ue3{)HPzhq~pcKzST&&Gkf|HlXxgO`sAq^CdHv24uxj?{RW)n?_{pZl27psE{GZ$ zX2v~vw9;XrT2>#PFc}>3d%;cYOpywGNTijQT&4j?_V}e6_qU90+G=+!x=GIC4zulL zr^05DbUky3v)v*dSXJDrwV8URHI*yC5*;YmaCS#36sP_N8+B^PE(zo$66Mt$c4vIUsU9#dQ}Er|(s$fH z(wk5j=$)(7!d^q#r1UO)F1DGyO4Z#e?{(6jl@V#_^nFFRm@Qi7F?4w+3rm*3)S_sb zy*^wX$^2U;-Coj7(~hbuntJ`@#DCagRqc`!A169?J!btB_|^SSz^_{WYKYWr!!}NZ zP#60h@0rEUgsL6=hzoptj`(VUXjwJA_9BTlhvjHXJGD&4B%3yV$QRL(gZz|F(t?th ziJQuI+BSvN0s?33b40BRR(FNt%P;@ZXstY7Au`s#3oW#LlNjh>mmMSoSo zqemu1Wump?0;wjvyYx%{z1#QdmK79K`3{P-Yb##2u|qs!V@ws+_%xjZAGwNXb5he@?z1lgVCM1q3vuQ$d%LXC*W)YN6tP&+o9VviJ=AU@?yP zT{NRQbhqRgjh=y}C9jHeYjnl0P7nZ^YJvbzozmVX)mM*0lqrpFD|&`hJaR-qom$b- zy(L%WUjQ(AIbC8!zcTe1`G@bD!tvX6_Y3rlOInG5X}xT}7+;jPOGYxLC3EsG3i!-s z)$5{W;e{U<{)W72JhACf*OhnO9>||9 zWx=nycj{a9G(z5t44007Nq5q>=TYzaIna5@99FoJWw4YZi5*-z!Y*J>uJF132}-*Rbk z$qBvoxD;)M-UWGJ5u3a=7P`(>dJRS2&;um*@1Y~S-DRZMz2nC^`rBqiElR1hxrnRkTI4wQ!x2NwjPE{yyIR%}(}Di|zFvCt;EDYC*lW zW0s8Wx1zl%9&^rc$N7^?)jW-t$!k%$Rq{qMGYEnB?%*j{P6S4EHOeNNmtPkoajY?O zPLKD(cDKG@Xj}XaQ){}zvO2x(b~#?gK5z1AfpPY5?ia1mJ|+S$f`fh{c*?KJi|lyW zYp9P2>U)Hk4jBBr3`8}ixfHy(tr*7I6mTEIg{27UO{J8De}CXiwQ7{t#U0^keKY*- z`DG7(GU2FE9$1Af9;rip|Fh|p8_y65_Otcu;Pgz}XRiOSQ~Q%i#vW7Oor!b6x!rRC zKj%Ltv;rgh2_SBXEH8nS1?A<~F2PHl_`KRF`1!N!b)2-Gd+IXhf8fEXl>dSUy-W44 z|6~=sw0$;|n<3$ZYTJGm{=@lY169O#1l6_AC4CoDVNCahHQsz&Jd!Ju+|qhq}DoriJ%q(9yzO0V1RYpI>47B~^A= z*Y#&#OLI8M97uq`kW1?iFr4`d7+OcdEbM8osF5Mnmpr};J-_526uyRD5c|{=YnsYy zx-%J7^B~FM%fKv`*yO*E;rf#xq*NkZRQu{a>#&jL(mj5Q7b@UB3imdoyVW^T>V;Eo z_{j+5hJKwTk8@i?e*Yu)HFAGJ@hr(Jra18_PLsMy@Mt>1a*` z#J0CcFTmOxNVfH+Ll34gs+SuQ)kc5Cebr)mA2Q(5L%v+3IJ9q7^+tc>VN9p*#V){M0_1c4ovrgIw!oHD{~*NcN*7 z5vVo$o_JwOxC8F-Fw_b%W}!OJ(C0m~a*z^Od+)^UyU$HZGRn#T&#%`f$W;Vti7Kwa$^h(}KH031o_0f)vHj zmyAY-CDO1BsBf)`DYH&eUC)X{(l1lDGw9@&>Z)HaCB>kvI}(eiGbmelFa%=mzMb(? z-|t=DRa-|f^V*jl_yy+BSl9j5djDHl4NeSO4kzi4ZPFwD$^41wToLZ>Gp2t${c6?clN|BLNc1QQxm>2wmrs+^M>w4}5 zssw13tNuVfLYBLzWXtG#$g@WoS^j5^$0AHLK-_2q54-gbZtUTM+}}SCNaL-TL(&id zI|vm{e`MfC;#lBN{V!}IOlviC{o9K(9P2Gq1wPhjlDWP4O99khoUjsQn+eYu&TA=Z z7ndf|cFRWYjg>31VH5`r1GjITG-L^N&GG2Da69UUPv<&BD>n*|xoo<~!3M1ip70kB z=;+EMzzd?@WEJoQuITI9y$3pwj%> z9OP$=Z&=n=eW`h_itZoiX!t<6)%zOBh2`Fbz#{fA2?f4J=h<-$CB4Dx%Buno)h*0# zvfc)_)|$DH$R9{}K9!}v|M1ft4%$FKX5wEqC8pUsZ$GhJ^I)Vf5LmW}VNh z?bc7ft`onSszdukDXn_E(OHZg*7NjZ@^Gu_pzhga>6q8<@DL~^UEQdbqX_+ zPi$aI?8dg6v+3t*o89M7KjdACwW{IcbzU`h%EB!f_o3ystLHjQ5YaR z8^bW=6Jb;qno*^6XZ!^ma+RZ%e?-DtR_D;*<-oJpkCsI&I~j8x@a^gbg*Bt-!cBQ- zdWn6zl#A-(obG^s=ID0XJf5rRb-jaz`h-BW4n9J=60;1pae4NUnB!^V5|wi>t|8TV*X(S67czV*w{T zb+gxigZ{-`Z_8bOf<#(LR{iI3^{hoLO2Q*m?o?q}B^Ed7Zk6LzIL*RVPQ?i~J?h22 z{e#Xf9jPSGjW234WE8cefHY6(Bf&SBDpq`Rws0SLxkR!!#L3Xe!$xsu^zUK%K92~u zLNOmWJjkcO{&Botsl~a3*uQ{eCI}?Cqcc*Ei{JwXkn-iH$KYxlh$M#`&23t%=93TT z#kJq1X-&*m*0D z1mGQea%Q))n=~GsC|It#+;uDDpUNu}84)R?nLy4_R@L1o+CeJzaA%u&xLYXa5m1Cf@iKT=1f+)8MVQ9x`hny<6_Zz+ktSgnqC?)tE#&GB+J^ zKrh9uFp1onyWrhQ9>iH>p2(7rqx<%g@=#_^h2md)a{ZJ~y7SYR{^67PH~Y^yrW?N_75LIln^ak%TsvVJ^X{Hm-kVxW0>?B1ZRd_YT{)W zmYH|2)avYI^E&s&$HdP$$3EhZHSv|(sghZy^FL*k2#{6QYtq>MVU_EVyyqXfcKeP& zKLx7HL)cb`04?rJlP1V3XAMLw$gM9>n2Lk*Oq~9XNK4TUtXZl?p`blNz04~~x4xb8 z!otsjt={?6p(rY$ArqsjK-O-MSx#9Mz1dvY(J!vuH<5g(RK2t`6FA6oM{iA2v*XiD zmlA><2k&LWF57?789K8@dfed^-Dna1Yv)U5)hk*Nk$Y_VHWrnk%92+%RlJ-sM@Eq) zDvb~{&z+!KK82UeD$H+vdymKmvE^&Rnj6bd^@gxRpuxb)_^Wue+N%?-i89~mR}b>X z<3Kzhb^g?MKgvSNfRl_89dP2#g+cUv~u$Iavx)a&Az8390r>b&ZH-x3Li3>g> z%jP8lMpeSkzfn=8qYb3u3u$t=Paelx`~@%DKzP|)Q~VFRG)bxVVSKsO(Zrp`jks&t zW#wX!-{zuH5a6Jh_IWFP!^mnOe4XDy2E8U+J#av+9z_JM&P$`ad|5+knY+J7%fYm> zroruV29B+)^=Dd6Lj7p8ZFykPh4ej5_&X_NcFSudly&Mq2x`DH$JMI~yvp=`m0v?l zd!5RE8wgJ*_eFkWP~pVcFHB0c_~~F;K=4s4a-}HK?1^zpsWWvst84XRv9rpYYODtz z96bhf0?*x5L)(2 zSRNC$JLGN-0SkF92)+=LC1idh|E_qIwUVE8K_ESitE)`1EP@#)jS-)ZYM)1*vP@*T z;d5Vw?QhQgb@fmy1Y@(uKmg0mW=48<*rh4MM~&i*SAQae4(V_FOgut+@mY1>b z!Zsu<+OeQripA4*nVU`E8#i84;y>?zYpfViDDrvf^~c#Y zw-wFh-ZExu6If$K_Wi>xgv5J}(N8GLS0y)|q^__|b56DzEiG4jD78n8F2(IGGClCy zm05rCD(`FCMX|dHPLnHg0xxYxk^i914{fdMrpsAv^q=?IIlBUCXQRwo?P3{dKF*KT zTd0l5XEO^2-f3Nh)9Rj`-^`eDKPrrIE+4#7LZJZV`9;f0m~M0)$|MG=GUYex4}QqQ|f<$o`HX$ zrw_;e@)z4b*b`1w;X$At%vM~^UWY4<+l6O574A{Y&XUD$oX-!se^BQx3@Cb%W3eM; zcO|`!q6N*0t{bGXu)pSylk%dy^2jw6N~ZB`sFObPsfzBpNkjT}UQ^P;p3J)BfT$_H zUBZQt@Br1~28`P5gB}h{+#%Xj00Mt`|T;mQwfq4!TlLU6aX z6F=-!84U!e*}794g`r;#v`f@Q+|m_)X!sOnN=+chG8P2IMYP4S>i?H4HDmbk zZD%wJ-^O$9<_JtS=gOIGkbNo;fs=##o0A#ydy07bPZ8R5(N?YPn=-j#YZ2v6^49XO zw^@OYCM4Ob>pgwiAw7f3R45S4nig9RGHihubiEa$FEX6V6Zw2W!hFi^Ol+8Gni;b) z6mMQyyJGV-TbNIO^SB#b@CSr`I5-&*^v)4lVs{Su2K{6Vg)4ed0qxi)W zSkpH_xw6UVDu+W_dN(Oor=RRCTLFF}j#hct6n?-ITd0d3Pa7DpN` zU4|ccv!3Q=_31%Iin2VJraQ~*8l>my#&3f_6govn7*`<_2QnTMZpfz?rTm3PS(Q4Q z7!Il^t12qXwkCV6>s@s4Cn#$o01?K(DwKs2m_#n8uML}C6n$%{pny_eDX4o;SH%Bn ze$~_ha@RN+aSueUQMQ2&kIj>-*NW|*J!$!*!il%7GKz#H_SpL}KRB1GJ^z+cU-vtO z>UXMdgRJIhX&Vysx7_i@nkIE&MX!G~|4|8q4KO$ZdG zJlc#Yu#w9o)h1Jyvw5ov-S%{&ZAlvSTOYY<+acDB*QUEmo}D)7B9Q zn!knak1(7H<3I9;PJ%Un2TG~H*I8O>+Oz+!<dQ&*sFJb%3&WAF z)U?3pLWgmfVynKS_Qvxf9f!}EqbAQ)jBBBjnfKD=_Yt+M&nRM!EsR(-tvhdQ>xWIN z15qLkM)BARSC-{Gt_z;nL6(P6EO2@ zKOOIBFiNP}L~YrrXsm-r5&}oe&K-dW&T4=_`TGu-a~X6m4L?>5HpY_eJWf97PW4FQ}}ZL5opHQDqR7!T_OUBk5FFaJVG^B zx{wD$;P&fs(uOV~FnK(a-p@FNpPsSnt<;RWQBTMj&h$9GOa#8Xy$o1yfEz{-YjX%H zyqjkh`UZ{^S+-1o92DVh0fGC38&mgU?q>Jk;OjQn2pm-YF@k-Y2$12|R%CwFBd@`~ zHwX~U_u^Z(5a^IToGNnv^tmswsKX}gP`3xO!*3$si5RZYQ$PyfOI7las7bIYM7?tR zBq9Q@n?)sK@vI%;4H{;evo+0*ugM5p`>mM@uO$K^I;DpJ$iwy(S+Y%N(jKB90~645 zVx<(gh4s^%1(m-As$A+>81ga3x<@Erx)mqk{;Xw+2y8$nGmg$ogO4Qw=<`yz4-=@h zcnoT_0?-{Ty>Wx^0pU$Z(d&9EF|tVnmWv2|8J3LI{6wJlD_Cg?s`~c1fwtYxU}vc5 zO-hQiP7(n5rf>y>93lYz*Gq}O9mIeR z(mAp{_yJ6pZ78s!FA}aed_TCG6h^plVxf1`gDv`CLz*l==p~%*@t9`c5!gf$0Tuma zELRdns5TS20ah>sSfQrAVW-zkLvWC7nqH3(?+^8-4d_jHuP26FL!n17_8(V-3A5~k zQRm!0u1PS>yVDR{->x0l#V- z;&ABdC?giShB~b9API9z0W(?S3Zni9wU|c)*zuxy(SI%xfmc!SL}1}D0divTWCk`$ z*xE+0T7jDC9U)#+K@pXZlLh2nOWwvO2nH2hZKp$q9Mv9f?r5rnr+~UNKb|W8;=#!U zdV)2!;wfQKzaZKjWWbZm6_VQbeU`n;XoQ;Wv>bFRRV6!*?6rj&dbSD-)PcB6aEGFy zQAxwgv|b?%Mfdrn7)) zGaapl5Xo0Wk|$*i;6_k$`@p3GK*BA;9ZU{1v~+ZBp3aNt+1I+z0PQ zkEFCq>3jA>Y#hL#vWq_fI;Tq(kBPumR*vMIJzNJ7s41Y2M^q4UqxQb^)bBN$JXb44 zAPLF$@oS1FpeH^-)vHYJHf){b8avmrpL(ka0DhU{Vcgro4p|V6bHPynIwj%2{W+8{ zL_l-R5P^ovUk?!l&5X;0BQUb51HGDj2(XI^Mzv9IcMyyxxh16@&Nb8g`v|WZ26w0> z7Z@xG)%2k;gmKWZqmVc7#p%;{=PrB;wxZjE6w!GDbABj1-jeGGR{|kIIF8S;97Gk{K z?s63v>>Lrbw>c55?08R=9L2c}#PPzaK4jrEo?JtLZyH~*yZA|D&~wIVeBQ5P)SnQM zUFrb`Ppdpma2u66HXgyo9NAQByb%iQnN$FDJ*Lm)>>+rl9ZTU@d;Ii?g>o1A2zC^A z8e5nyXo4M`tCB`78Bl^4;U}DrUK$78Z?9uj5zV-l2?p!|fpo&rY11O`EVn6pQV5_K z;Fx-xI2{N@?LsHN5;{d zP1ttCVHBWJhTl5K!9n+1iU)ee*>R-#0&gQuBSuLnIUJ0OdZ3byw>dZC(Mi8RZ`LFN z8EX%apnZi!Fi8X&F`K?|)VT<5cu+uq4f4=N<`+I?JP3`f*Tl1EZ-iLFrEsO1b9t|k zt+m($_|Zx&|0E$dBrOUTbBKt}n5J9>qje7)w0nT2cvTS_bF#LN=(&+XFv{N1qEJ9? zA*i;S^bR#kXBD02!3g9GgSx!ILD%9kM$5qdsVbT+bpQBeRaPC07+|gd(v zYt9iDVJsWB1M19r7lHZ{Y$Xey3p*Sm#>YXMobP6zOvi(*+oXf&0U>%|(1BVRKH+#V zjZQYk5*#MSh-#ePm7e_=>^#P1^vR3`D)j)8LA@*(8#4=~(AzMv- zf7|H2%n`b$zASrYB`-xM3V9trNMgcVArco;GSrpuo(aD$+Y9Pq`fP*%G43-*Ec0!E5W^ngDC`q2DFU_(`Q6Iw%V=< z9Ez|}9f#vA?DSe*8a?AYwiEjeRcp8oy5#Ao?Nx_HKj*)fF6mhHxs(X>!Lb!-=ie-E z5=1J@N9rMa9nd7aBlYez_`wKvkb42uf;{a1tO-4WA9RAD4PqgH_aq!pFB8_c5Re@h z==|S_fCIGl?lB{NZ15iP=yY0QfIq^**YZA+;6OJbRKDDWf=S=#;7ZrV>K=lEdKr!F zQjhJ0-3xIBT{YK_hoFhIL^kU~gQE5}39^!idO~i^BRAP7XfX89PLXgdg>c8%we+M` zHx}U_zNn$g;LIi#9DFOIB~WzIk==YIq~RE`9D*NPk)`pMYR19WQp3~+!2F_3K&zB( z22LLo>BxNtH|7pq2DmOvgT0wOnX!e#qQETX6*Cc-hp|)G4oo79YLUN2V&NDY1Rvsk z)NViwjA>>shzQ`Sa{U!Gu&BkPBQUX3D)ttYEhG?u)%c+(BpT8A*clh1u(tgKaPSwj*QI$GP;c!eYn12#W29d4cq(u9xOxwV+Pv#`P6*~l(fY+af z4Dd}Ox23($Dju9(fl0pNYc;kX9DjxHNZ<%Ulpo8o^BjS3?(3m5;pk*A2O7d zI40oejdaC*%_=OiNr@iJ;5( z1t%aVYrF*F^m(@gVr!(F@Z^b?P|;lX%3tkd8fm=h(~Gu2RVHQ*fk!jSKy*O#~Px zkL8O_TfmNai3&d_HYL!$cWn@gqr&Uho@`7nAq|X zi}VJY<~Gx?V$VSdnt*qJ;%G7bVTJDE5bKlaEj`D$;Z*`-EsoWlaGb^no;H;&j?pff z&P$m8^wM9dz%DS_PQ-sWY{CzSV$Q^ZtwD2{^$I?Q^%=S6m9C-lm!FK@W)5u4OOZN8 zd1VwZ&Zm*qY0g6;0yJmrjWu9*tAEE_Z!tO|S3gD_#vkuHvfyjidVd}D&>`#^z-8g) zj;?F5&d&-%wnm~bdo9d90*zjAMw{3tSdp0`@g2GK!_F^u4P@1WMH0|>^aDx!yYmoz zhvT&$h=PdI2|ReP=MzDS-h5iAKfub~T_H1~41F^%;R#-D&j7)F5LW{Ut24~BOOh<& z!;KrnOr-h;v5u&&C>GxvT?|AYv|^gRA5Dpbw~KiC*Gp|wsk!csP|cHv{M^MnQ0^-< zWO;l%?Tp(lgwN-~ZDFRIgB}V>{5MOF?~WcFJ@4~R-R@(}25FqholvtLF<5%?SW zZf!Bvtqa+tRIN!=&qUp58cVDq0@Rw%D@Z*OW9-LEuIXFE!0pW3PINIZ#t&m_`?Ck7 zMgtQYK7z3toQ@d71=r8ZpR}Smy|XU=>fI{IxL1ap-D-Wg9i8AVw^~zYJVpbY^eKWf zoj-Sr5c=6qT0=cjMBJNB@(i4ltO5BXXDL6UL)%!*&oWFe-zi<06Hbtao z?ZG#Wk2Fm}CK}_8oqD&5$S`M4y8Z`C-yPP}`TmV{@U<4HTC}2&Rz+G6v6fL8i5r1p z96%UC>Oi)svPW`Um7>K|5vZgP6qx}cBOo&>Iz!iK-XL&a{e>G z=M|t;!_NWKkdC6dWdFA-TC3pUpeN^LD_61K{)F1{ORd2_4gP<#HkuyF+ca-l+M>+Rv=&y$_O?kKB*`dTs0DFQivF;CO z0(9s?+$d4?;DP*siItx!^VRqMqac6j?F@7vkAlfhu{03J1B+ zUzej^pnTu?w}j3gRJRmZVL7TV3k(OeAD}#lV`CLUGTRJ*1Qw4dpM=nn+7+$vnUkDk zoVoG^s&DWeLL;byJHx;ac|hSSNNT=9dG8f&r~FNSBSo)vF?2xe?D4o3UAoG!TA`%k7UpDU-7%Fqc4rRJQlqbgXrDNt33o7Y#9Qc@P zfz9#Ap5iASJYo3WzCd}CUWsoaO;l!9Zjn{U3ALziuq3l$-#)3&#Y(Tea8aZB6n-FW z%;=i&yz#;x=+rY}GV~kQm$*CFz8Q11#xXK9bXgxcCR>HISD99mk_pT!mDRTcibWF$ zwX^~Bc+Qt94Bp;UR^CVg#;10|iWXa)5q!k|dif>52enXrsy})k((u_{+=r(b?}Oo4hD2kJ5Z(VAi*CV>ZS^!2|$$iF&Y}(Ri>LZ$wR;m{L22B z4|aU-6#69qhq4bVTFYc_@Qk3P|FC3&BisfWG$6e9&xH5xhUzoApyJpi=uhRZ%4XJM z?|s|en5JaR8HX0c{O|76F7lG^KVlpWrm0Dw z+}S<&-w$WKU1aMLKgr#Ft{q=kM!&6R_0S_}@LWk9QxQV-@yjh()OO&8Tv{~a=5Q-Z zlIM|M*2@C#8p*=onoYe*L4O#2Ve8Wn^GwOCm`Fa#EA|a;n!*6-qiui24_y?)V>z>q z1s7)1(m9Wd*WUNpJhR6B_e}adZIG&iN%cIMPotY7k!+Oa5uO zcXc*XJ%>SO2j3qY7Uv?>W~kt#@{+tZ-Rt6h~A{#G$q7h;5m6 zeHRxKRJ;F7{c*E%uwiSQtlMD`w^-xx|dk#_QA@R>Y`bHl>2tle>38o?C zbR|xZ)?Q(-&Q_HQN5w@wC$v!xE$+c=JKvZJ*i4`;bd7zJ! zH2lRwS+dU5-;Z`T4AW3Rgd=yZXeBP6vU42s9^NPv4}10BQkgCoGMLd$@aaWBWy#p) zYr#U@QPd>aFMOu}yw|HLTw>SMa_vr_W!76O8>zKARiyTbg>2bfvP2G@X5IGgNb?rX z>O8Su=l?+7^UnB9_e|$VAQajqU1Msysg^Y6OB`db_ZW=7QqkLBSPQ+uRgtCSTjdd3 z`+oP#Z9DOaKG$@?Qb#v+P6SQtHjnt`wrfRnLz|2~%6C&8iz%1=(PQaanKt)RbLk6a zar{3&;%?`!8d>8=oODv)W%L+P_`GRTYGp_C;xH*lIpP1|Z2xaN`FHk@_o{4jYgGRv zS)1ynm!W$uybc{T2t|<(K|5_iH&Yv0H`3pSKu^YiZB6dDAUWP*Z&|Ni5>n=OI zw#vkZ@Y>5dArIO#A6_%WQf?~KezxWE+zEx@O{{}Fu;?^hi-lta&il)JrYk5Na59K0 ztlK|P>qA|qL^gwpGN`KvR?lv{GtT6Ro#@^RoWJZ0ii#>p4A72a(?;_iWXaEO%^uY? zoa#NYAVXK1F#z;&#x};W_die&GGJd6hFu~jJ+%;jMa*_s?PMG2ZdIH zX4t8Ji;1V9=OR?Uv_Tif3Km`I6b;$-R?oHzfSj}iPykojcf%^2Fvul&?$bPI$aHx= zUqYUzhspE5u>U(0h>`en#*f%gn@Z=HVtNq*%9aTgMli8epmCUJ;S#P+q88bZl4s3a zuy-tv19jCWhzYhkWv$Em8yp6lAo;nfm!!ynltEM&NOKMOe47(U2Y(i5Ob%z(d8+~N zSZ+i3QFG;l_duj;`7o;Uv%3CdLp#L6!R)^Jcyr`NJgU1OK%(4wZhe_IMRquWA=gzf zem3p$eF+lPs(`=uB!CMaT zDY1wA&Xy1H?-yGCEFUznkEw}HAoiS_IPE;;WWn4VRAYxTXcbCcu2=&)a1_&3)2G6rXei4;ReQ3MUI3^b#B4s>iV-2<55f6~aWKUi37fe>Pg z$`$$sbPhqGwT6^f?B=NZP~EY@8G(u(C&&HqgO-Cvo}!ND43kifk|~CIf*^61AI@6I zR9*&5q7bhDeJ?csi`o~MdV~pgC(*5kiPHbHNegZ8Y`}j|{ZSm^tirrq*4l+fnDr`5 zlPfwtN9ND6KY>ao-7kk4Se#&eyCsrDFIl~H2J2S*RYwb12(#Iq$f#Nj^o}N8;b1V9 zJm{?R0-DR^?EdgvGDRj_*-9f}CphTePvUmd252-a9^j1sul-cxWzZy}-~2{;$uxZm zIeMb%MtOTClyRbsZ^M2hv2lhg#o0V>>KwSgk~*7uDs&s4@iT+=^!i z_Y*Gj0ZapD@_xC^uT_qGwZBRVjb*>bvgPx-utpe!d!JB*$P73lKUm;adL?nb($2i0 zl30$&ecV2J^aA6Xv!1sK%3-%%F?#xE%Yp=!xsHG~5@T;^WHKp6bgg98KgP$f3h>iH zMJgk21nD?u{4u3>U4nYa0jA75o>jjsYOGn&iYYL$0E~3t@y&g%G~8ei)lcu)M~%-g z6;ujhO%eV8@|?m=vch-zwN|?=qOD!vb>+Ba{ZDqJ!K*U|{gE<%IuY#$YRkM9Asn1b zcg*_;#r6Om_#A@|5SD?KBU?=xW?)|76_ak@12q&Gqui`^>qeT@!CoM+7Rcgfz!BlT zU6M7!?CK-Wj?Hzt65s>g&89$u)*WL|;ayACP+^I=TOHzj0lI;cF^(OijrjbEUM^YD zlDP#`Iv2C|&RWHHtN0QiafI9gN;klfv9@<0#o@bn&+%N`CW1|Zi|%l!0VcoERsLUbOyiF`{R92cmWdD* zGwDMc=@D$M4a6v+Onx`0!U4I&K41L?ic$x6&1HqfoiB2^)FKw46Y7DNX$Nq8KU6Tx zwB-eti<(gexIECb`~fP;m-*u>?6<@t@o(>F6hlBs;(A2=8R&%nd;SfRJg6v$9rD1X zqQMoihQL8e)l2KWDb)ePxd0{S#6yUO%3?8!8gKs`db9gdKQ*VkzcaCT_GRyPEjLsA7GR5pvwIX z7ozx*53`M`%i;2|T-BpbBhQUt_g7Vmq`xYD8}NJE-LmJ#wGZ!8@cqSQ`v zLOq(jQ?;RM1*>!+BJd~O&Cy^W~{y5tsu45^0jdE5;U$-;O zb-+Gax1v?nH%i%E<0DqUXUo`8n6bR3egriHI+nMGcp+P}W3OnTfv(c~2lF?iW5KAu z+^lrZ2!vI=%z+12czSumid{}kUDzG+Elt}J3!=kLRFu~$utBq!Y=Pc2rGE?oTs zm`4l$J=BeK<6&SMaa&x3=I!j3V+tVgp!OOSQwzNEsJ6-?M1hJ?Tg~6_wSm&0DMy(A z3-eHIgz2^FI&PQ*&{?J>UcO85|HY$k!Sh5tHJ;F!nQ}Q09xYb+fa8AxiY*u(@hp1g zSDom&C;sy->JWXg7~jBe20!SZ{%ytbYvfC4_xuioR%NLbfq4a);8Wl^MP(h=W%HU9 zE%(AdA3A`v!#2M%Oi^5?kq6!c-&1fNv>E`fPh25epoOPyfyVb zJ*b)oW0zA3$^{5p!o|*63Mk)qGpk4^=vN*Y4+9w{&8vOm>;0j=W(x;v?1;;}Srs@RgiTRkwW*r4W&#cK6JikWKm9W=T((;snJ(fT_$xf;K` zEa?}31$p|`p!r3Bg5IG8t#BpbC677pc^A~->wSOOW-@vHIOEBxXGcAI<8U*7oJu_M zY5h^C@PYUY{krgo>C5Tes7+}DG|~&+e*15BPTnncqLLjBZ?*2EFX(SB!YD2u))StG zltXT@)c_fSJDtjX=Gr4zqId>PYI%T0%;uhTehFP?b6s7xr)}Zzfl!zav`W{rhqcx4 z$qv>fM(3J4;`$w+4o0a%ci{TQlYmzE!xugYXiT#s!TO^ZPA{lc;AX;CafDF|d)qvC zQNT!QyO17(SV2AJqP{=cK1#&oUO>Lw(xnBrGSRpS)~-Ebb2ST{4+`PI{o-czY-u*H zaenGlTbXX{lH00R>Gmaq(K2Ag#Z3aVP`WQN{;&7ad^C1X*^x;SsyFNl-H`q0ug(eQ z>Bae|Vv$@z=?D2YlU%G#*Y zEmjjdPJ*|xjxRh%ZQzT=*&;i(M$VnyIr2WqPV+)kdm@4rtzfz7xq@0$_<0bN9#-cO zA}nh&6@?}?Gz2f==GTvLv}dii;w&fUV~yYGetTrhe4|hQq|fAMWy-|IN|@x@RgzzQ_!II{C1l4>S8SQr<#i}syJ8Z_Y?#NP2NtI&xn6$Zk25mWO{OZ5Cp5z;dABn6xv% zj(*Esq@*CO(fuNyeR$ue{)lG)-&i*F=y2TmSAFOg(a^b9{55klBWBJ%%Rj&Oj^5)* zaiK>*GfDAqPJEm^tQb7LPv~UqiK++jKizob^O_uZq&qgJk`aUw+WCz@U#QrG1rP$D z4N;z|hT}^t8y;W6wDOh!Pu}sJqhFK)3zQ_j2Xesv7>ocv0<(~_k;WHWgIac@4&yFQ z4jZinkCoLwADN6BX&GkzUS3_w4Kb7MI7zr4{fL{YKQ8;ta}b9R724p;5aE8{J)WdT ziPRnRCU?U@2yOs}Nb;`CVhsdz$}l;@j1QEveq+?6GcGjId<4af72qIUIDa)x^s~c% zjNbld7*m6=unJf$Rs^fgxj_{lAAokLa$U8zL%gjG$fB&hoV3_$|L5P3+|V8gqyyAa zF~tqF1ky_?PQYyc0GhJkFH2%PMzKwvn>{x;2p(0N7Y;(0b@c0C`svP5tQBZ@y`aQ# ziC5q9SP&O@Z!Ix0)f_-p^{yyh0p2RAz1Bt=O|CJ&B1~!KM=-`YQNhvgYc~yRqe)A^xn^N}r_WQyEg=AuYH{Gi_OY zSy1fM)`D1;9^S;gPxbRS&Qsa`@WpYbAD-82Y&x0-b+9AcdEVcwGSV~rpkmgVIeM+S zMCH$+CO?$i`Ri|}EvVYHMODd2$=a+jL+^Ly%UmPtg+Cu`-tqm4*3~3B__Uz2djP1S zXT0E=V%YW~s}o0+7vlLEe>59>YN9znGEzs?LGV0GzG|a^J+< zK2xDrRmYmyDF}r7%$1Wqf25Ps`;p8m-spVT%u>bKxu8$s6|~PowJN5VKsz(NkC@A- zCFgwPjO0!Zx{1x0Y?hhofq@JS7F~Tl^+r3@?4(ORXf_Piid_nyj{A{B|-6qH4V^@`_pwNO0bft4Kzj~)|I!lw>a=tn7`6t(cQZa z!GV?tG`$UN4Hh;!_AQj2S^0vVNT#|8hswYNVe~H8@ zF<~8Ej;dSmWtcxT`s=0mnFaoT9k3;_v|roZ)I1<8V_nr)y{Qu`)!D`U$IMska=aJK z)SNtEAF?LS*vYz=8e_}ktPLWSf@h|4r1NrbVoZ2SK^d1XKIrZ}7?Rbw?1C95KB(YJ zWA7FaPse`^cJ|^nM^n*F9XA?w@QBwaAHD39#`hxF-ws1tR(C5W7ubVZg! z0INxm3w2+ot9Dvcf{Km{Y|b;&QSplQCSH}1RPZK~D4 z1p`Y@HHI6ou1QE2^f~A&z?|7U74fx$Sk?x!smK!&kA0Otfv=Goet?0h84R+J%d0Tp zEVDt)(mN?$0;bhcm-iLg4}6@cN`wam+b;MpfX_S6#j`=hYjK;MQiEzCE-0jntksh! zZan>_wj&IZkGQdHREZzSTRaA89{@6dpG~Wk#!mqNVhfVi{b9dgM~ndxl-MNNpd1pX zrB~Os(dym3Uwv)}wm0k}xQD)fxWu*1*sNoHc{-{iG)i)CAFO*2zxL|Y$Q{O-285mE z()UA3aMAI7dP#HonY5j)xI0s4wilY04?vvVz!{>eeptQIFR7W`Tpc^TPZrADxgpUj zCCxjvep-u1Fv)D~%$hdkQpb^E;4h1NQ|pB<=k_4x^+Cp9iBrU7ah=<{Rqj7%ILq@| z-F=&^T~iDkeY70av%N)XK;N&QRBExnd-~%C_b95Eb()GY#Amdgc){6zCgj$y+QMDq zzC-6MjMm+Ka`vn^YeQlf4%}N{n+5PAj@9?{k%4f|tD`Th&1(NFeegjh#V1i!Gi^Tn_3zr$5~8}6vSWl_O2t*b_y#q`jqbfb2RUlp%P z?H2p|MXVr!P0m+ODooAHvZ@z%I#xfLNQ($J{1%cUwZ%FZ@60KJWB=T>$NjgGY)&V~ zi#?;Plben;wD1rK-|=`<6qQ{;5X9LFAzquGkr8U66Sklnvv7JTh!{5 z*PyGU#(}Isf6lGku#7wG4*(t&HYhYc>N!RpNNR+MyHuO&5Qs~--su7ERSFx_N#;k~ z0OlQLFm|~Hx8MZ^LG*x9bhpklDuS4z9R3W$aO@NC)S!$i_k`^nH5E!R&_n!+ z3F4WWBDYB;t{M6>7N?30TIzr9iFO;>R)S{3T&A;$ht6g0*NOpG*ogmgfTXG=F>!SZ z{)zMK2_ZhUp|J}W=M7x?Bbw5_UtRW89l)1&K ze|5(FK`|lEe8xRBSN{Y6aei<@o!o3eHX&eDqLYz5?YUv}jBo-s^MG9M*|aiZB-`NQ zTPivJoorC-#{)n$c=`bMP$M%m2Vda4H6?gyc$hGfdGJ&nX3XX1rp2A9RKcDr{~pbH zOcnzA;4fakw{)U~3r+G*luV58FU6J?i6faI)4wR0CxQaf?Ue7zNiV?dymM@h%A0Tl zl>DyKgcI|bm5)(8XY2vugK1X!Czw$L9Fi(xxdk^&LRLIj`b1mjI?9R`3{G?b zZV0F}por-wqfbSkym!Vt`nSs;LHN9l;y9I85TMo7PGcaKYxHd-}!iv@{sEw$Y@mkfK)2^H&It+*JyuMOJX2%|akZJD3|0zs@qzm8@0s30^>LUnkNos^< zpwC9K7Z-huG}_5lh{EW*9WOq-)#P|##PuJ(uz8hO`aEjb6;P6_xi3r0)$Tzx?)O?Z z-aZ1Fy_F<7;BO4{^{9LiqgoLVgg?k_6!rz%1TrZ059+=9Mxv&e5z)poAS@hFsD z!~i4gN}#)0CiFPFV9R9I7yb1CyADeqN^jiUPi`HRCfp7#rtg1S@)#W8J1%(H$SZaO zh{w6uIQ&A$kiS{c0?LE`N$v~=QeTFOO3W-y8G8ByhCJ0@shyEajVOX*Ot0Hvm`x3~ z1U|y7;Z7%(VqI9fVm7j}3iLA#%lqf+On}LyB+>|W))%8JTVzj=v)`p%Vz^U4i@IH6 z)X_~5*`_ZAowGUpe97r{;3YC&(pTOs5rv-XvNkb(GTrGKScLzIjZ;Zcy=RsWTxRz2 z&Qp}*GXFzq6V{5k20-`RE)1uWUO_S=bp8hAP?yS~P+sD5*`b;ruo&VoP>a|36jXC9 zxorNa$k0qW-?KU&Uk}WnPyz@$Xza_&S1{n+EGbaAwFz}m#_2nPxB?%nRQt#Tq=)X1 z*N!LEHY9|hiNx=P&?@Yn85baWULi4iAB39dm^xSWWP3E60u}#}xsoX@IE?vA$-Gq; zm=mAobz;DM$Dq&dkbw_Gmae}fB*U#o&$NiB!Bt3`b^h0*5lN*kQs>n1HC!-gzSq^|1zIY3awit3oAT*^1` zM|+?N@sZ4OW!>Q=Hb~>RgWwJ;e5>J}MZa0Gy znDODu&Sd>rB-TC#$#}3Yr4#@+kXK!R?*T~{{I5^P0Opv?ERh}gGl(ZSi0mamB1Bnm&3p^XZJFP*WCI`blj%dPjy{qoclp6h)oe9>(%|R2a>afq( zkT#$l{3`YS8X`mDr}>BKAFXsP)#BLRDGC0t@J8M5Yf2s$@H(HYx^Ck1@P$Lx2Ko&K z_io{`!97i3z$}~z*Mesd4MCJjZFU~gJxrh7?}_6L z`ZRQIyp~<;@REBLyg?wKN~f$VnRPo1Sm6Lhq==(@W{(tDH%fQpFgwaaLAAG1TpD4h zIrZ+FtN_g_!JmBpJygfKjK+{4-~|l`zNl4*(c%+J{Y))}oK6|JvZ8Cd4IhVE% zvVtF6>OYd>fzO>8+@lyqBXi~tw=M3iT9=qJ6uhOeXBWw(kTr#3$I!2BJpq|SBtC{Mv|f*57<`_{WFb+eT)xrcydmY^?vHQXA=9i z`JhkuU6XdNfOnJEW^_Zzd`(*0p`E5*Ob6jGhr6gg4=4%b!?QbN4WGVIbF+u^c31G~ z{gS}}Ow9*Rf>Hl3W)>yjf9w&1ea656z}0%7d-|{)s)Hbz_6H=Gt^WAffg6f#yB?nW z7BO-t!8P=HP3$`m zEv=1+&<$n7afJUU+waJ+7Ye+GI>qj%Fp1aD#}ol&wLB24wz2-xlDTQPg87+L<<6|M zI~KFHGUIn~l^_kQfqce%sPc!_QO6Lntz4iG)~-GO?}Q!XT}MC1#hlG}Iy_kfEx(AR zii~0tn(*43aRXBOU|F}0*lXLVW!q^I$(k;B zMGKtJ4Z#8lDjSULUA+4{^yW~Mq*;T?1gw}5s_ffvmuvQ&p!`IsBV|mdlwWS_wAA&8 zdv7Iv^wf+mPrq_6X~99FN(K0zVjsATojC(i)DU@sC11U$Je}1^vHNO z#YqNMC=@Xrr%|ZM*!dfg9|}>aao|}y{`ljP0I(uIio`m|9#VK-E&1IF^BF%5H0=)# z15|_vcb}QDiKio^vx=}4Em~~5Cihk|y9p+k`y3@aT=OTwc;Pf9Ja^M#(`+-LU<=vp zz@B`t=Cq;GGWWbwsY;LTg?W8sBxz11J~XnVxM13;+MW; z9dnbnWChmb$-?>_Lnk`Ekn}bDTwT{^bsRmu?IA#}ccH!gem$+=k7ULi^z!@n(U33h z@IL8-9rRaS?tjG77iP>$B951KiU5kXlS*^q>o08E!#Vxvvit{)hTPYW37gQ-H*hjD zfhJN}TRcjuYoaHD#$@MhTsoeF+)}-W@M2zF(E{>OF{2iUgC%#49h%!m^lxR)GIW-s zvE*d%iF8H%9)Ma-J;O$z=2ADq&W$c}`x~MZbkP}Els7sD25vxjybaUAZq0^10Q&(j z^VpjDqIQloyS%nH=lF>$;pmbZNA4&tPTf=6O}pF-r`C2n5+e+>vOxW?MNOY@Vh3mg zAikcSfSoAg0XLEHqP)){)DgsfNzb8?`txMNcaykiaE;eJ;LNW|A$mvwY!KPk82%@% z+gvsuBv_20TPu5+;G5jtzTBrCmhzST`1)f!dG|nnoAYG2+%k5boiQu0MtkR+I)BUWA3>3=HQVagfl0@o5MG3Vrj@V23kL1@HY6{pTr9hsor z?Ti08>;k`$$-6ClEQztqwWcEMZO-L{B^hx9O>rTBfl11%?)?*gu zt!$Mtsotlxwi$-x$C_*@xCG$~yuDo>fSjr9CX7mb?5OYId636t!fe6pm)2sB7bCTH zFUW(819tr8D%*iJC-NZHA+zCUc|j4rAh`QE`TZ^)@nm$eH_OG^yWilF0v>!##Ejr% zDg95`arLp^Jp8@9l76n{6pn2UGiGm>Mq^%|F;>+%qW#ChP)v6zaTBQ2I_Z%6L&X@8}FblwP6edUMHlDV4I_oZ-nrPzsCQv z7L=geVB6$an zeLNEUcJ)~lFOElq=Aim)L+uHh0C-AR=z?X?V!BoyLKT|>Z5i{*0oBJrZ2xxJE-ZHS7YNHwDzUAk~#4Q_EBopeNC z5JCnXyiUN{sA!zV2&v@m&V#o>e4^CSXaY#y0HM`ep69P#(b~MCrM+l>buGltCJpi7 z)?+89Aprvd6~_Y+N)}b(WZR_LmuGvY)PmU)}QrC2x zI}@&-V&u;WO&-H!_)vt~7e-KeyeEA&uvF92A zqQPEuB)7`Noo(0ij^xc9M+Uv>_hk<~?UC-L;<4K){Y4&g9_WcdcZ<09hC;-qtEvq* z{O~0oakD&*xC`y+Fp!>%~79P#;=Y(ZN5HW8qiFP z`L}@_lI_5Y#XmZhX0D}DONH0P-`yS|Ft_o?Em*Ij&7b$loWwS)_+cC+UXV+uX7?|1 ztLX2&-=b1Nm$_xq=Kgcg)2iw=HZjk4 z_J@v`INS$TYR%zZ$S{y#GxBtd<|#Ag2d6JP+k+1qTvby|EIpzg7@f0-bLgIo+MQf8@6T$51@cf~VWrFu3l6bc8 z++BR~RW2a1GjK4r$%i36=C;cLy6c>}B&n(V~pc>XM1qgYeJ8ayE(1JJm1 z`=Uk#X?%a;IPUA3nM%e!TcZ-9!$RtP#gRrVsV82u<&>^T z!t*eiq4A=<>;-+Os>DEylf_m8cxRe(BTkSYIGHtlP~~0Nm>v}PlB^KYx-6oxPDz`^ z9=&xRwuuNu0m?n14;Mx~y7rk@a9vJrcsj--UWvEjGA)@KnvB|<+O5soW7X-G*xe1b zebPV8imzEr>p@_&fqZu1`Jn%ukOrc-|LGasU&9gf63B~)9gk^ZK+rb_+7yRrOd^8XJM4mBWi?)d8`G>axnF|oW#XXi_{D`h{?Tt^lBoL?v zu7TvSh6Cz>*){MDRd@%HW{%}%%Q>Khb%3VL9w`K0qclahSzB?VxFPhLdki+n>VOL6 zrk)Pa%pSA=0Wi0~ibMiE)=V=i1~@A)^fh@C>X3~9=wI3@S$YwPu4bz4=n$Y}aTVNM z{skak9oqodY&sJI-fB=;4YK1gbDEELR`!qPA~Y4SR|4d3er&&W18bA8$yVoW>?Was zVdW-S=i1gzBHLqzV0!G}8!~^yHRW{4nScNzJp)Jh2b=XKZ?NhV-z3B;CgoFDddj7j z!iztA;be*(BnKFU@4QGb#lFaDf5oP}$e-pPUZ-4I(b_J5{J-E1 z24?=!Fx&qzw&ng@#3B8`^OdRn0;C6&gv9=65}VZlxH0IJ%LAc|8vAl#u<(; z?WfJwv?Li^v#@oszgUZxA|S{dk(P$FxF?MDcpW`+A(L8!56$+O8Tn~AcQ&jQDe)E& zN@7~hq>m&u^f>idd)8r^JY$Wfk|x&G_ig%(zlLpA?)D&q)Rg|NG&kDaDzOV2C=6gO2=(&cHbDR^O^c{=3;=TBUc!v`m>UMoiS|p)7bm)(1Ty!KW2Ba1b>yr zE{@?hvDcrtacOaF+v!h3FV}qwM9&qi130ytkYq|NB3*g+B1j4IcgQRKX;E8HzfYviZJZV(*Cytx<$RFuHK~!Em>g^K%yFY|+ z!d(#q>>Dh!`)QN(=Lz=aXlIYgxT>0-G{4lAtG65emfMaA--iO#>bn}-Yx^-lP3li% zNPpfO15#4Up4-%$b~$9$>Y&c8G|K0K6TiOiOWCdK&4o&rgOg5@`Fr#vgYu=fvVn(k zvj#_j1Hu*3H(mQJosSsKHZ#qz;i0V2SmvGY>K3Ndx@XdNuuUiLMe==9y`5IMN;i=H z+FBFZ&{kXmj$%YQ4h$^_b%0D{*vtI4r*Y)F?i9PqN;W91p3~{A!B)zhv97YHs7qt4}j|xmA%w*fGquCif2ScVz4m5Ee zmnJt|zSH!<4r^G37aj5SJG_rFW(7w4Q4_-{9SuknpkT%wRQ#jMIclM$)Tq zf*isB*@j4V{Ki_82xjf8`6xCk8<5x<1h@Gjo1%rC{Y>Pjf3G4|l z>4#f9v+5m3DbU-opB*PD13i1p$zU{6!Y8SvZZfElHwhTEtXQj5MwjyA)Sw*Kds1O2 zrZNv;7~o)lI^Mt^icm1@RSB9|R#zZ5 zh&7rYzkCX{KtAXY5b&_(q0w{hxg15H1o%mpb2O<4cKE6l?t)tSlIjsR&D<&7_`GV9 zkmgYQz`E$;jd9<2?nT40p7$WjbP*Hb!FPj!$X9yK$gl5@fOXH$dO|_6VoZgDPr_kk zZ;?$W@I)>YY%ZUV<0dT;j!dTgz&i`ff^%oC-jEHHbU2BTlwPCujr`pN8q|JrwlYNI z@6^LvHR5Jn&Z3D2O+l`Qm73lhV5HLZywx@mZ@rNEf-cehv>YAxI^7yb#oY1>HBd}& z1+V6Av5tMg?jep&Q5hLN#c1Y_?~`@U_0Q)y!A{IFZQ_2XAGx1@wxe*~W^U@U#LQ!= ztdU}OyNkus3)9w=OU+g*T90d;ISIZSxKLl<+1>ryisB8+!%hA3Lreg%COy3_zf^Ay zIH#JrF1-@GV^_%K?MNAPqr$B2r@c8v&13JyIdMN?{Tb9T_I(@NQ1zX26-$&ky;(!u zg&{*jR&88WV2LPaq~nBBRzzyw@7^a!d53GwMipDL>#8%iQrGO+fnFV~dC>OH_oR#z z1MtzgF5-QW>S+Mv(gU{H6f+c#m?!XhcIeiJ5e5e;oI-E>#ZdY72I7O@9kMyK7^XOx z?=xPCQb=B$Z=Ee}I>=|v79|uQ%!3LE^!<{2esg;9WqYl9t+)Sl?Ny zf}|552=!eYbM7;Dyu3EL!D}P~`}?7aYP7pI#|3jH_2c{`YD7+Hn{hWZCOb;E%lJ7Z zNQ9fZBOs{KaDQ)=BC69x@RnA0k4G?^YTot0siw3(;eAS%CnTtkH9nLU6O(gnA0f)? zxQue))QXm6Y+h5XDeb{iyYt2SeKN9c0d*i1ht}O+Qe)mP?h4G*>wbC-ia;NCsBnJF zY^x)>qEWZw)XT*&te;wr>;+#;tVIipLKF=`s2HkY;jf_Qj60UZUgrBFDH0bzt`+HZ zoxiYm3PCL0dXW_UxFNVM+E50P9l4t4Hxk|ChIZGaiGX1kD}kf2Zm8a(_pM*S0j6vdz$oWz~%|0oxl8!ZLiuMx@N?@J6W7VxxZVC z;GU{>U}yU|+%M>SbbdX234(Ex83Cj_pd8WUxCefILwOv}TR`$AivfksJkW{8nj-+- z-3PFnL#$On5kbTuhy^kaP>+7+mz>WO7~GEHSSwNhTgY5!ISTc-@Lz+FMG$?6i1>k7 zhR#l&OQj;HyW-0MOIas4I}IaYLfaVrgo?Cc@dbP_s$65d_UAqXqc^yG6xcwiG)}AL`JNmU>7py=v?Q zF9<;o1SeYgUYMn}wjixeXL`CLc!L+q(3Yv#e^~&uXy3AQY9cA^lvQ+Dvd^jHunEu zP630H-1;glS)>qhmYJK66e{qm89CRCz}`YJIdHB~WX3e3OieNL+Ol6CwVOeUyR;0Stl-BYMqG&5x zqtJ~6NXqIAxGtj{01Z-UR1|l1B2C2c(syeYU*CLK5Px>E1MQ#SU<&z!>W& zN3q;%j34Q{cePo0BxG=O*mAfRV-~)(?`v=Gxxsp%fwglv(@LXd^K0<0kB*r3_C*^CPTe^Z0RP~J!HxAR+_6=Q=7+iQ$D)pU zd(098(05$F@)#p#N$ZVI7~I2m?XTd>4YZEO5e=Q!M$FS|ALwq5h8%iIMm9|LKC;5K za|Mz5ZbLts%O*}hq)D5;cMryffA(7Z%FESh_uG@R@v+{&QL3}Q-5%;2Hh?t%YBaBg zC@bzZ%NWTviv@8qGC;w-Mmw-%y4Bkne67QR;u>4>p}S#^@{1-gppKC&fh_lBU;G4p z^AVjQN5f$xxXWBTT6y0SB}yw<5`Lm(wbaZ(43SN1GQK+qH%c@?NzDvsk!@SXqumYj ziJM&?EEV${vZ9PcX&hsO2OA zmw&*DCy)*>a0{E6a?oK1HneE21jZPxP8a5a&z?!NhH>{aLn17d9uY=Nvo=nQ`BaD} zI!qem;k^Hb>WNE2OQ2jId!5|y9Q`Tc)8EIZSyBNX6qyz^P#$vimZXPs(W6E!!qEh-B#jGAW>#b-gF>_svOAl9ta0Ol!oi!Bat?GY5L z90-nj~8JPr7hF zsw}Qr0+*_XX#nfbZ{k+^6(c<1xtDWbpx+1-E>C>*`5cWnc&Gzx-4U3bFW4mfxA2eq z)ok>so6QeaHgBJocZsUp|IpqZMt1a+8m+t6d@992&$aRKi$Y}f4UPk+*6;qyP@==p zw>%#QI#?4dY*pc*b+mjwe#zc%Fgg=)aak&wPVT$s|8g%i_#nX;8g1L&px$cu^X;jc zw|(zRK0~1^Au4smSMAIrB=F>SDHVBQ@CWJc*em zW&aWN!50b}o4@RUt`9zpRXNKItAKt=q$_cMsRExv3_3aX9(Az~sxI+9quqxC2BF)M zC9yK5yAJOt&lxLrN%oswjgt(1MkA(EV~2}d--7&zYMoR2#zpf{)SAH8b5t`{tCvYq zz)5^RF7D-(rdDhkg+}7J*jBA@Ob81SqtN za|8wZ73e%K_+_3H5GSxSe2Ma~9pGi2OmUFOaj^(AnG%K^ut@?&ygH07Proa{9#=pQ zUyh(XF#9!Z^2YZ_tr%*HZR7pgDUd>IqDdZYE;;L>9+Vt_e)1(2tF#x?AK?XcZw+ZE z7Jf@cUFp*06wHd&uKpb79lsbxIQ@=wu9Kf#+;Kcjt(Vm&!}}^0_)XhJc63z@S0cP| zGIwUJJdi(d&>YsU1By0qZbQZBaJb=~zVE6>9?d%+e-Iib{%ji)0D?U= zS^~ZRG6PV*7}YC;a35gy?FtOKR;A;riJj|&-E!$(Q_}Q0b93!t)%{&v^SHslftPdh zdA-a~i$OQO*<1;O6)d7AP&VFqU1L^rdE3D?alr6$DY>YH9=}u_^bmjz=T2P+9(Nj+`RIS?DZkL&{dp!vkpO(lX&9{CqOU-sMy=<3S)lLWZ zrlhE%_%6A3mCOEkhY)_mRS@+DUgKiBjBnKakuG+#h)s^yJ#3Iu^&oUV)Mw#V9a=nd zo8jY-9XMW4r*25x2JcjbDZOril2Tv=-+mR&?z7q29N%yLg;@iT2PjK({cIhvBZYS_Vs87_(gQ|KYy#1WNi0s*uag~DH_CmSnY18 zz6?AJ_nxM*iq*@v%*_l-b~hqev63n#G{)kZG5UetR)7}Hb@=2Yw0KWP8ug`YnJa2U zHctvpq{TsPCcS(Z^@RFz;#+d!BNis+fFWcf+J`lo)^DNh*>)Bx#MgVRnxhFE9-y^c zTiXw^m^k)ia&cH-aibUpjXsttVO?;!)Qk}yq)3GaY#g|xO0qb))u|JSpD}+bnOiz5 zOmlAY1d}9f+67i2_y)FQ7J%=VaRab#Q6lyA2fv+;Z_@^YElc5P*tjZi3%eQZ(yCi# zlQCR~8(*7Vap>b|(d-VRbjSDj28XeYP);<4lahZvYV1!|WkIGx5|yw`OVp6tN~+Vmiy@EQ$Qza(b5%6&!y zK?Y6tpcp-pTAn)CQ-H=3{I!|0NAVsZblpcipqNo@vQ;= zlxBLRef_3UbpC%pZ9++-WY7vA)$%Y3h@GQNp??$2F^;Ts13)ayY7r0d`}rhPSzqljp&{#1=T6I%GQ+ z0mhq0K(WW10gwo<@UK!|VZ!Nvw%IXj(E>xz6(v2f0Ax8J6;R`#rbY!gfNKH&)+6er z!~M!$sP-&?V^L4Ro=YI7`tT=H?c2}-aG}7Vo#TNCSRS0eqD2x>V5q&9#Ntl@7Yppt z!d!>gNO6*+^D5>QLN{M5CLsGTb3nJ62R)44C^a7dCqK7$)k>G*bd3wbNUJ1Evl4x` ze~Q>4>rK&jO?l?}=7mH4bk?UK#*WzxT5oFjsw}GH4E^<4E`Pi#OFM_tL9m{?pIv}e zf~9^Y<2cdgf=;T9na}RFFEw`W9pLZ>-V&e(mb$v};xpwe1ul{Ed^d!a?A@L7+ED)SE6N515ZA^zmT3XX%athwH)z3c4o zvYQT9bbZMng7{j`y?Aq>!d&AoMC_tXj%X=0Qa2CA>Ytf1`0%d$EYFUj3qZw5nP9CB z?(DHH82056`3B}-k>xff$u;YYI4%{9-`KD1Dws)tv>S!!q@<}bYd7K=N<+H6jU!%!~ zP%LDg)_PLxRSlt4mSD~Y?1I{D%8$Qkr{VXdYIv_}`ltUXgyZ4A@5XDf1!pkZ+%b>y zh_bmoi{WT;mvks8P40jJmM;>N+zmnx5BF%e)fS!0-q;?1CEa9C{G~M@LH(qEEnNe-A8oB9sOx#rF1L*u-2CBjW^fmQyNi6sVp>f1bbj3@jt_QF2JvXh~ z;^5D1y!E8OYOR|8$t8cj-4z`-=2YzC*#xMSx3BsAko~96vvoc|s+%dZ;neS$TZEh6tDE`hAMMFo5^5s$AsM8<(hF)gaOdDF^PD(uaHtf&|2Z;~ zy!2TvVLeq8JMREJJ@?8*9g_6oz5XLW`kZpV7{U$u&@0ede0r3%6~<2>pVEM7r}#1U zp&%vV-1-bIEp4v zys)x>clDa)^X%50kM=dw{G)aLNX0vb%{XP54|m;!7&-?+7|1cIBodrWT!EJ8c7!$D z1&A65{mp0w8Ofq&{KJ9ppT~gIxz|)>oF7`F=2RODblVa-9(UbBe`AtMom*zml<|-DWxPR67NKaTU&;476l0yZ ztRB^N`mFmMT-YeP$G^0?@87J@63>qo4|$!-xm#|rIiXn(_5Tt1s_rq1taprU(@}fM zN4#4hmXENe<*k;R>T?Q(0BAEW1v-DXb~kp^*Xs^b>$T#E=Q3M^&dWb;N@`_x z%ifrv{U{oL29n2|ugwJ;VzNH1X|g)pb&qg(?(3Kek;4@|I#NmW2@fZdd2V%aH3)BJ}vfBXAqI=T6p&U910VAA1AU+(Wch|rMsaAUL6pFFqmfHtJJkU!Bo z)VU-C=Nz7FGDKK(uhy;REy3x2ohSmJZ7Oq2_D1O86W!B)&KdefRu4r7>!){cE z6S+W27!`+GKyFZ+r%$77N>8JF_*I}IO6?Sl$PsF+2S|s?7ki@wzG`=o0?+NsTEwIN zMt-}3lj|6A*cIeQnWP%!w5{Zv|oo6DbMomO>T8 zS_gb$zz)EW+Y{Xd2QV6|gK0dlddSSCsIbha)&*B5VluS3x_#N;Li`||HOuR^i48H8 zfSxQ1Vk8gIbnVYj0HA+u*TN*pN=!%wPP&=r6#n>MC%b547F8r@$eV78DxKQ|i(!Kd z+{d*nukr&X)NL-8Lw{feq8MzfqJrJyo4r79SK&gXZrYR;{l0SYUq6LStjC}w4ZhMvx zSHtZr?-0($6Y0ja?D6dwc&T218o3e!{Bp>rkN*kZ+o#BU#1NTb(k=U);u)}sFA_xr zPTjC4N0@)Xl|btP8~8=Z2!!{Tjl$ipUR$7kxXWC`fBiwb zv86@+3Gbn-xvMUY#u}kT7rYfx82j^dT~g@pD!p#I@G{+J^JY9unDc+80H^{uxtN#r z(KpD{pHijb&E+#|nWj^D29;nJI*=4M?+!MQCUaA*5IvJ5G+zptmp5!tm>-H>ay{iL zTC4Myh3P)sTiLn0zLp9B1%<+Z+!#G2rQ zugSF9WrdN~MmD4<_V3Goh5wRiGvSl?GVa$MHKO4R9qK(kH`#K0IF_C%zPG6ksaqJj z18PIty5R8qqcaD&IED)QV}2QxU=!B~Yp*Y8t#%As)V;s!*W#x(5hzXL&cKlNpa zbL_g~WiQIwgU&j1oh-77-0NXYb{3y;-w|~a60jf?!vks0?M0+!Ew>pDtF6OXq4>VQ z(*=)SI{+zBqU-elA9AkB16fsc#I;hOx-*-L{k!7hgoz8nfd3P0DId$XHkT1VPL&Xx zW*0T5PpPOFJDs4L>t=M|Q%|PW2T>n4;_JPzga63@Az4PGJ%olkc9xtzRN<7wB=3V4 zHm?t3ZwInYaf#&sD=e21xHvVq~ zBHT^m&2EpI#h^+hFJg$jl;PFea(sHsh7w1tHWSPcqWc%mhCRL`T*d1W4AY~DBphr) zTRz`yy8#>%5MT?m6Nmx5ns^Ekf{0!r|I}isr#+gy0YwkR5{~G%ixc>L8Ih0cM2jb` zG_sr6>lviXU`q ztN?dr@~G6!ARVzSB|)e6OCUcp*wk;sX3~=Wy^)%xAO#Up33-e-{DfgI8I?;d+>KEo zY+S!hZ%0bl17!a;{VEbF5;{}lupz?TLRYVtiySpXx}9#HX1K#kO1Z8T9^BN!EL7irFGa#ZvQ(J1Np6*uNe54Q3BsEz3GQY3( z8<<4eWD|&ytS!KL!zB2X@Mjgg+msK!@kwRkX&lgs(eBLm+I{@fTqqzO^G9Xyx%XvJ z%<*_yTd89?^IeCr_)q!I0?pXy!NVgHid`?IOMTdJkW0j7TncciC$9uJ-zwN&pW}V? zvel)V!J75fENiW#9wRvlK#^E3xM{AT)53Wis54cU>8 ziG_VP%W{@MhCQZlA7}JR0c)z>RHId(Bt)g&i_Z+~lYUe*)S2;iM{3cTAzS#@Htg_B zfo?05#vW!_aVi>8ISEadxI~I-FKI%DZ^;~i&cDmjQ_SvxU=znP9fHHk2}X&PG9Nop z+o3??kVP!ov{qpn*bL8*o8CX|Ym_3;Rwey?`D8nU;+0ueKi9r|3dPR|0$ls-h@Oio z3^wFZ-)d2^JKFAC(7Fiu88xPMGRp^{?B}zu#@Z@0W`Zn7AK-I8bXuQ+;;i8L$LPZz zQla+eXB)HAn9jwFphsWjABNL1AY#w?A{?;3Us8bW ztLc+yLSRS6D4Cpfg?BX8%QE*aYvATR!McYS5Pa-PRPXC3p)}Z#Z}Bj(M6Bq*uN3u z26UiUaRY*+{K1lRHDPCG%XjIJIV_!wQNSBQ{JG)HrAtqmYS{Wfyp%nqJB3ZLid-TY?--Cq zF9K52JBEF?DttDxoN7K&*FQ}BMQH%4oXnwmGR#GG9j)(_#;aWbzta?ms0Yksrceh@*U2q-2y_3v|f@?L56cIBaoBB3_xC9js zj!fw9o@{q!gOhh-(E?Bx2yjz{u!4#o->NpBC=D9Y9~4<`%HORLl?akuzV)~86@?ZY ziDfNKuYlGSaxo;gUMv#^G{k*4!z+|uJD=5>*Vp~YU$s^6?4AtJ4X&0jvm3--R<^54 zD5*KGiJM;OUt6-R3-sQu3pf8ZQPrktKQ#T1+lA)8>c?h8<=o0?enFq3A-%ukU&07m z>{L?_pKbHvw#R=0IO;%7g~hY++U-&6q9x>_ViaxSUXy=9%QYL=!PmA)#kCWCiq~n) z<`Q66`G&DupZDTk<)0w)gco!5#g?lH|*f{vWF zkc*N5L_|2WkP`jVq50s~nu49zPHTJ72GFP&1#`Jpu3P(m#OjK9>955pe?UO!owBP{ zB+RB7pdvTg=HUTmwWHe%&Ee6ew%L{STyw?c^FO_@g$=-KkZ2&I%MaE{Rw>?2-F8Ciu_shMJ0Rc9{NGp&v5++l0TGC23aBM4>*HKzYf9^qgv>9pfI?@7U0SAfRD{$7}2 zFZ_88X?n-lDdUVItD*Q8Y_C*2B-lJqjJI7NnP8eC-!V+imY-;f`%sP_mPY~F&eygd^W!+%kBoDZ5Ay)@91dV@kQ1ilU1M`@&@^rBT zLb^1g`%LutlZsbFQVn$~%9N94|MQ8 ze z|Ds%{E19UN<(lkNa!(FMs&&gKFIgd!I(ABG)sSC+KR}0IoG1!v!i{R0fp{K5_};SW zhKGiY2)~khEzlcI!|dV0jS?182MSmCRG+F8E}$=&#dHPFhmdzFqmP|DN#A%c#=i67 zNt>nl^`(Z6U-d1Po3?~!E1PHL<%y@%$_s+VY!M^wSN#gsas3Nk)+fxzYnF0RVyS(7 zH;k8jbGO2MehzWSYm4QNcV?fpdKJ0~I=omY0J@%~&n`i7h;&y#-jt!!J z>&1{r(8Y-%i#-+28F+||1`h#sGs^m=%vZPOpnV5`F9+D#Z8{hLy`9}vc%A|JT`7KP z__4^FNK@Gb!O#y)mrn22fss!?^G4uFghjA^8W9cT#GO@LFqsQJ>kq!p-ac(0TmNEH87U3!bjfP*kwdRrNKh z?j_3&B?f7qn~rk9N=GHNZLZQA=0>h5Dn#fj>9Sj!`89lS;ECJ{MBY^$QL9=V6L-|- z)oq|BpA=udS;(|<{ilj~Vo*|Z=yZMRTg5L4>rB*(O8!#y_@^z?KZ`QIssfeSF-MjD zn+mDFNmKH_wL89yt0J7Yl=t^k8I-;8-%aFnua|OrZ}m2}lfOr0bT7BPcy zjYxJl`2{YddQ|&J5GpbF-=VkvEU$f=l|lpji#c8rI9c}sAE{o&*tOigtAJe`D_@2 z1B+4Kdb>ebBB1h3BMx++01_WMU~@LLhJrE!Ig^dzG_owG+TGo5Oh=pS#BYD@F&OSa zGtgRp{3zvm2d6?}!2I<#*i!@gP{~D`=}JS$aH!_*?+Ii2Vu^97mloQg_!S!C`WZkV zm4?JH(5A|v5P3dFwzu6$mQ&Xrc zdj|;cHV7p&++X-)>!s=FZLkihb=b_DR`k=|&R`TnUlWtC0N5Za{uc!Jg8&iyAz1UG zHus@LBE0wE9WAPQI+bg#^%GKWfI1g;{i}WnrY?_7&~jfF5{-Ql5U&g;t7LcjL=piv z)5R=~+O4Bl*mx_0LJero{nPE}$@0wX!iIO9#x=}-Y3kL~V|KN%9qvA`Q7hn=V-fln zA%^00w_5SaBz>mXvR6>;x%^lC@du-u?9RA;gUhq=Jv>-%QWyUkBZlW}Ed6aq}EPE8(@ucIJRr%eXnxWk2eLlwc73{D~-vTQh?)q!uFYpSe;s3nUXM@FMI z-+IZM>Y?Py%Z<>kh1Cw3kn?VQ$0!Gg5=l!d!NL!bS7xSl+>t#a zn(=hr5|(f?2gN7=&AQI$T}l+x}3K>(IfKuo$T-Gm>6r+Pnc=Bw8f zPJZug%SH_jfVj{4b4c-=Hr1vVBDfDM6k{HBiHuwJ8N~uoP&5^igPSfjR258{Lv{vW zyf~Eubv|SSQc1aIwE^N3Prnn^K)Ea3P)P!=We;W>%?j-fjK+1Xbe_y8AJw5V1@Pc&d+Gp9zX-`CU|P3I3qJ$C z4AVKHTJyE+)`f|+lDOg|O6?1m*^WWx+=d+lO1x0e2zt?jrvLu;WO#KH6*?^#B0?C+@Q`GCvZq<9qzM$< z^!=IZ@y1Wgj~(ccL=ap25)CvU|a5Ml4d1Dx$DmX!b4 z>;q`>IC?}dyrjB;PEAGIp&-+?<@0&fyR%iSh}^yqMrXb=yHnQ$yf&wy`A>ohVuA<#p?+7ErQ=5we86xyp;Hlq(T8y35n_Utw+DQ|n$A&E|j_Ji);=DI_w#EB-) zPq<{Sg;rb0!6It zAmml>14yruC-U0r2V&1UPN2Az*W`}f8BhOyKw+MOKn_b3o2U;eLSc&G5QGQYJ6~2& zURhSq$+`#AUaSvcRn^>xUTjFAy8M14;NC|IcE!zS>nW#)069=X9IVAxz^B9a`)yZj z5~o%&<~{5+;k$7MSbu>+S3qiztVO%w z(fiwx 100: + r[0] = 100 + + if r[1] > 100: + r[1] = 100 + x.append(int(line[0])) # requests - y.append([float(v)*100 for v in line[1:]]) # cpu, ram + y.append(r) # cpu, ram return x, y def generate_req_graph(filename, framework_name, endpoint_name): - x, y = get_data(filename) + x, _ = get_data(filename) + y = [] - filename = filename.split('/')[-1] - new_filename = filename.replace('.csv', '') - plot_graph(x, y, f'{framework_name} - {endpoint_name}', 'Número de requisições', 'Requisições por segundo', new_filename) + for f in FRAMEWORKS: + newfile = filename.replace(framework_name, f) + _, y_data = get_data(newfile) + + y.append(y_data) + + graph_file = f'req_{endpoint_name.replace("/", "").replace("?", "")}' + plot_graph(x, y, f'Requisições atendidas por segundo - {endpoint_name}', 'Número de requisições', 'Requisições/segundo', graph_file) def generate_resource_graph(filename, framework_name, endpoint_name): - x, y = get_resource_data(filename) + x, _ = get_resource_data(filename) - filename = filename.split('/')[-1] - new_filename = filename.replace('.csv', '') - plot_resource_graph(x, y, f'{framework_name} - {endpoint_name}', 'Uso de recursos', 'Uso (%)', new_filename) \ No newline at end of file + for resource_index, resource in enumerate(['cpu', 'ram']): + y = [] + + for f in FRAMEWORKS: + newfile = filename.replace(framework_name, f) + _, y_data = get_resource_data(newfile) + y.append([data[resource_index] for data in y_data]) + + graph_file = f'{resource}_{endpoint_name.replace("/", "").replace("?", "")}' + plot_resource_graph(x, y, f'Uso de {resource.upper()} - {endpoint_name}', 'Número de requisições', f'Uso de {resource.upper()} (%)', graph_file) + +if __name__ == '__main__': + endpoints = [config[0] for config in API_REQUESTS] + + for endpoint_name in endpoints: + framework_name = 'ASP.NET' + endpoint_file = endpoint_name.replace('/', '') + + filename = f'data/resource_ASP.NET_{endpoint_file}.csv' + generate_resource_graph(filename, framework_name, endpoint_name) + + filename = f'data/req_ASP.NET_{endpoint_file}.csv' + generate_req_graph(filename, framework_name, endpoint_name) diff --git a/scripts/testes.py b/scripts/testes.py index 10eb847..93c7f5a 100644 --- a/scripts/testes.py +++ b/scripts/testes.py @@ -2,33 +2,17 @@ import requests import docker import concurrent.futures import time -import sys import os -from graph import generate_req_graph, generate_resource_graph from math import floor from init import init - -if len(sys.argv) < 2 or len(sys.argv) > 3 or sys.argv[1] == '-h' or sys.argv[1] == '--help': - print("Usage: python testes.py [container name]") - sys.exit(1) +from common import FRAMEWORKS, ENDPOINTS, API_REQUESTS, AVG_RUNS init() THREADS = 10 -FRAMEWORK_NAME = sys.argv[1] -CONTAINER_NAME = sys.argv[2] if len(sys.argv) > 2 else "" +FRAMEWORK_NAME = "" +CONTAINER_NAME = "" URL_BASE = 'http://localhost:9090' -BLUR_RADIUS = 5 -API_REQUESTS = [ - ('/image/save-big-image', 'POST', range(0, 10_000, 1_000), open('big-image.png', 'rb').read()), - (f'/image/blur?radius={BLUR_RADIUS}', 'POST', range(0, 1_000, 50), open('small-image.png', 'rb').read()), - ('/status/ok', 'GET', range(0, 30_000, 5000), None), - ('/image/load-small-image', 'GET', range(0, 30_000, 5000), None), - ('/static/small-image.png', 'GET', range(0, 30_000, 5000), None), - ('/image/load-big-image', 'GET', range(0, 500, 50), None), - ('/static/big-image.png', 'GET', range(0, 500, 50), None), - ('/static/video.mp4', 'GET', range(0, 10_000, 1_000), None), -] def send_request(url, method = 'GET', payload = None): success = False @@ -77,46 +61,57 @@ def run_tests(endpoint, method, num_requests, metadata): for num_request in num_requests: if num_request <= 0: continue - ok_responses = 0 - bad_responses = 0 - server_errors = 0 - cpu, ram = 0, 0 + total_cpu, total_ram = 0, 0 + total_time = 0 - with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor: - url = f'{URL_BASE}{endpoint}' + for run in range(AVG_RUNS): + ok_responses = 0 + bad_responses = 0 + server_errors = 0 - start_time = time.time() + with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor: + url = f'{URL_BASE}{endpoint}' - futures = [] - #with requests.Session() as session: - # futures = [executor.submit(send_request, session, url) for _ in range(num_request)] + start_time = time.time() - half = floor(num_request/2) - for i in range(num_request): - futures.append(executor.submit(send_request, url, method, metadata)) + futures = [] + #with requests.Session() as session: + # futures = [executor.submit(send_request, session, url) for _ in range(num_request)] - if i == half: - cpu, ram = get_resource_usage() + half = floor(num_request/2) + for i in range(num_request): + futures.append(executor.submit(send_request, url, method, metadata)) - concurrent.futures.wait(futures) + if i == half: + cpu, ram = get_resource_usage() + total_cpu += float(cpu) + total_ram += float(ram) - elapsed_time = time.time() - start_time + concurrent.futures.wait(futures) - for future in futures: - responses = future.result() - ok_responses += responses[2] - bad_responses += responses[4] - server_errors += responses[5] + elapsed_time = time.time() - start_time + total_time += elapsed_time + + for future in futures: + responses = future.result() + ok_responses += responses[2] + bad_responses += responses[4] + server_errors += responses[5] + + print(f"[#{run}] {num_request}: {elapsed_time:.2f} seconds. {elapsed_time/num_request:.4f} seconds per request. {num_request/elapsed_time:.2f} requests per second. [OK: {ok_responses}, Bad Request: {bad_responses}, Server Error: {server_errors}]]") + + client = docker.from_env() + client.containers.get(CONTAINER_NAME).restart() + + time.sleep(3) + + cpu = total_cpu / AVG_RUNS + ram = total_ram / AVG_RUNS + elapsed_time = total_time / AVG_RUNS - print(f"{num_request}: {elapsed_time:.2f} seconds. {elapsed_time/num_request:.4f} seconds per request. {num_request/elapsed_time:.2f} requests per second. [OK: {ok_responses}, Bad Request: {bad_responses}, Server Error: {server_errors}]]") record(files[0], num_request, f"{num_request/elapsed_time:.2f}") record_resource(files[1], num_request, cpu, ram) - generate_req_graph(files[0], FRAMEWORK_NAME, endpoint) - generate_resource_graph(files[1], FRAMEWORK_NAME, endpoint) - - time.sleep(3) - def get_resource_usage(): if CONTAINER_NAME == "": return 0, 0 @@ -147,9 +142,12 @@ def get_ram_usage(stats): if __name__ == "__main__": if not os.path.exists("data"): os.mkdir("data") - else: - os.system("rm -rf data/*") - for endpoint, method, num_requests, metadata in API_REQUESTS: - print(f"# {endpoint}") - run_tests(endpoint, method, num_requests, metadata) + for i in range(len(FRAMEWORKS)): + FRAMEWORK_NAME = FRAMEWORKS[i][0] + CONTAINER_NAME = FRAMEWORKS[i][1] + URL_BASE = ENDPOINTS[FRAMEWORK_NAME] + + for endpoint, method, num_requests, metadata in API_REQUESTS: + print(f"# {FRAMEWORK_NAME} - {endpoint}") + run_tests(endpoint, method, num_requests, metadata) diff --git a/tcc-express b/tcc-express new file mode 160000 index 0000000..504b592 --- /dev/null +++ b/tcc-express @@ -0,0 +1 @@ +Subproject commit 504b59278f6024219814649f2715b70561251c65