From 2a582cf741c849bd22d607df47ef554d046752f5 Mon Sep 17 00:00:00 2001 From: JuliaJu Date: Wed, 24 Sep 2025 01:25:15 +0300 Subject: [PATCH 01/13] Implement ASGI application with fibonacci, factorial, and mean endpoints --- hw1/app.py | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 222 insertions(+), 1 deletion(-) diff --git a/hw1/app.py b/hw1/app.py index 6107b870..55754770 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -12,8 +12,229 @@ async def application( receive: Корутина для получения сообщений от клиента send: Корутина для отправки сообщений клиенту """ - # TODO: Ваша реализация здесь + + def get_params(query_string: str) -> dict[str, str]: + """Парсит query_string в словарь параметров вида {"key": "value"}""" + + query_params = {} + for pair in query_string.split("&"): + if "=" in pair: + key, value = pair.split("=", 1) + query_params[key] = value + return query_params + + async def send_response(status: int, message: str) -> None: + """Отправляет ответ клиенту""" + await send( + { + "type": "http.response.start", + "status": status, + "headers": [ + [b"content-type", b"application/json"], + ], + } + ) + await send( + { + "type": "http.response.body", + "body": message.encode(), + } + ) + + if scope["type"] == "http": + + path = scope["path"] + query_string = scope.get("query_string", b"").decode() + + # Реализация API для расчета чисел Фибоначчи + if path.startswith("/fibonacci/"): + + path_parsed = path.split("/") + n_value = path_parsed[2] + + # Проверка наличия параметра для расчета + if not n_value: + message = ( + '{"error": "Number parameter \\"/fibonacci/{n}\\" is required"}' + ) + await send_response(422, message) + return + + # Попытка преобразовать параметр в число + try: + n = int(n_value) + except ValueError: + message = f'{{"error": "Parameter \\"n\\" must be an integer, got \\"{n_value}\\""}}' + await send_response(422, message) + return + + # Проверка на отрицательное число + if n < 0: + message = ( + f'{{"error": "Parameter \\"n\\" cannot be negative, got {n}"}}' + ) + await send_response(400, message) + return + + # Расчет числа Фибоначчи + else: + + a, b = 0, 1 + for _ in range(n): + a, b = b, a + b + + factorial_number = a + result = f'{{"result": {factorial_number}}}' + + await send_response(200, result) + return + + # Реализация API для расчета факториала + elif path.startswith("/factorial"): + + query_params = get_params(query_string) + + # Проверка наличия параметра n + if "n" not in query_params: + message = '{"error": "Parameter \\"n\\" is required"}' + await send_response(422, message) + return + + n_value = query_params["n"] + + # Проверка, что параметр не пустой + if not n_value: + message = '{"error": "Parameter \\"n\\" cannot be empty"}' + await send_response(422, message) + return + + # Попытка преобразовать параметр в число + try: + n = int(n_value) + except ValueError: + message = f'{{"error": "Parameter \\"n\\" must be an integer, got \\"{n_value}\\""}}' + await send_response(422, message) + return + + # Проверка на отрицательное число + if n < 0: + message = ( + f'{{"error": "Parameter \\"n\\" cannot be negative, got {n}"}}' + ) + await send_response(400, message) + return + + # Расчет факториала + else: + + factorial_number = 1 + for i in range(1, n + 1): + factorial_number *= i + + result = f'{{"result": {factorial_number}}}' + await send_response(200, result) + return + + # Реализация API для расчета среднего + elif path.startswith("/mean"): + + query_params = get_params(query_string) + + # Проверка наличия параметра numbers + try: + numbers_list = [] + + # Получение данных из тела запроса + body = b"" + more_body = True + + while more_body: + message = await receive() + body += message.get("body", b"") + more_body = message.get("more_body", False) + + if body: + + body_str = body.decode().strip() + + # Проверка, что данные не пустые + if body_str == "[]": + message = '{"error": "Parameter \\"numbers\\" cannot be empty"}' + await send_response(400, message) + return + + # Обработка JSON из тела запроса + if body_str.startswith("[") and body_str.endswith("]"): + numbers_str = body_str[1:-1].strip() + if numbers_str: + for num_str in numbers_str.split(","): + num_clean = num_str.strip() + if num_clean: + numbers_list.append(float(num_clean)) + + # Проверка query_params, если данных нет в теле запроса + else: + + # Проверка наличия параметра numbers + if "numbers" not in query_params: + message = '{"error": "Parameter \\"numbers\\" is required"}' + await send_response(422, message) + return + + numbers_value = query_params["numbers"] + + # Проверка, что параметр не пустой + if not numbers_value: + message = '{"error": "Parameter \\"numbers\\" cannot be empty"}' + await send_response(400, message) + return + + # Попытка преобразования параметра в список чисел + try: + numbers_list = numbers_value.replace("%20", "").split(",") + numbers_list = [int(float(number)) for number in numbers_list] + except ValueError: + message = f'{{"error": "Parameter \\"numbers\\" must be a list of integers, got \\"{numbers_value}\\""}}' + await send_response(422, message) + return + + mean_number = sum(numbers_list) / len(numbers_list) + result = f'{{"result": {mean_number}}}' + await send_response(200, result) + return + + except Exception: + message = '{"error": "Invalid request"}' + await send_response(422, message) + return + + # Обработка запроса favicon.ico + elif path.startswith("/favicon.ico"): + message = '{"error": "No Content favicon"}' + await send_response(204, message) + + # Возвращение ошибки 404, если путь не соответствует ни одному из ожидаемых + else: + message = '{"error": "Not found"}' + await send_response(404, message) + return + + # Обработка запросов жизненного цикла (startup/shutdown) + elif scope["type"] == "lifespan": + while True: + message = await receive() + if message["type"] == "lifespan.startup": + await send({"type": "lifespan.startup.complete"}) + elif message["type"] == "lifespan.shutdown": + await send({"type": "lifespan.shutdown.complete"}) + return + else: + message = '{"error": "Unsupported request type"}' + await send_response(422, message) + return + if __name__ == "__main__": import uvicorn + uvicorn.run("app:application", host="0.0.0.0", port=8000, reload=True) From 17ef938c5e43c5d9473ee51d3f949c787e393474 Mon Sep 17 00:00:00 2001 From: JuliaJu Date: Sat, 4 Oct 2025 20:03:05 +0300 Subject: [PATCH 02/13] Implementation REST + RPC API for a shop --- hw2/hw/shop_api/api/__init__.py | 0 hw2/hw/shop_api/api/shop/__init__.py | 21 ++ hw2/hw/shop_api/api/shop/contracts.py | 76 +++++++ hw2/hw/shop_api/api/shop/routes.py | 286 ++++++++++++++++++++++++++ hw2/hw/shop_api/data/__init__.py | 24 +++ hw2/hw/shop_api/data/cart_queries.py | 148 +++++++++++++ hw2/hw/shop_api/data/item_queries.py | 96 +++++++++ hw2/hw/shop_api/data/models.py | 46 +++++ hw2/hw/shop_api/main.py | 4 + 9 files changed, 701 insertions(+) create mode 100644 hw2/hw/shop_api/api/__init__.py create mode 100644 hw2/hw/shop_api/api/shop/__init__.py create mode 100644 hw2/hw/shop_api/api/shop/contracts.py create mode 100644 hw2/hw/shop_api/api/shop/routes.py create mode 100644 hw2/hw/shop_api/data/__init__.py create mode 100644 hw2/hw/shop_api/data/cart_queries.py create mode 100644 hw2/hw/shop_api/data/item_queries.py create mode 100644 hw2/hw/shop_api/data/models.py diff --git a/hw2/hw/shop_api/api/__init__.py b/hw2/hw/shop_api/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/shop_api/api/shop/__init__.py b/hw2/hw/shop_api/api/shop/__init__.py new file mode 100644 index 00000000..b378e3b0 --- /dev/null +++ b/hw2/hw/shop_api/api/shop/__init__.py @@ -0,0 +1,21 @@ +from .contracts import ( + CartResponse, + CartRequest, + PatchCartRequest, + ItemResponse, + ItemRequest, + PatchItemRequest, +) + +from .routes import cart_router, item_router + +__all__ = [ + "CartResponse", + "CartRequest", + "PatchCartRequest", + "ItemResponse", + "ItemRequest", + "PatchItemRequest", + "cart_router", + "item_router", +] \ No newline at end of file diff --git a/hw2/hw/shop_api/api/shop/contracts.py b/hw2/hw/shop_api/api/shop/contracts.py new file mode 100644 index 00000000..34a42da7 --- /dev/null +++ b/hw2/hw/shop_api/api/shop/contracts.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from ...data.models import ( + CartItemInfo, + CartInfo, + CartEntity, + PatchCartInfo, + ItemInfo, + ItemEntity, + PatchItemInfo, +) + + +class CartResponse(BaseModel): + id: int + items: list[CartItemInfo] + price: float + + @staticmethod + def from_entity(entity: CartEntity) -> CartResponse: + return CartResponse( + id=entity.id, items=entity.info.items, price=entity.info.price + ) + + +class CartRequest(BaseModel): + items: list[CartItemInfo] = [] + price: float = 0.0 + + def as_cart_info(self) -> CartInfo: + return CartInfo(items=self.items, price=self.price) + + +class PatchCartRequest(BaseModel): + items: list[CartItemInfo] | None = None + + model_config = ConfigDict(extra="forbid") + + def as_patch_cart_info(self) -> PatchCartInfo: + return PatchCartInfo(items=self.items) + + +class ItemResponse(BaseModel): + id: int + name: str + price: float + deleted: bool + + @staticmethod + def from_entity(entity: ItemEntity) -> ItemResponse: + return ItemResponse( + id=entity.id, + name=entity.info.name, + price=entity.info.price, + deleted=entity.info.deleted, + ) + + +class ItemRequest(BaseModel): + name: str + price: float + + def as_item_info(self) -> ItemInfo: + return ItemInfo(name=self.name, price=self.price, deleted=False) + + +class PatchItemRequest(BaseModel): + name: str | None = None + price: float | None = None + + model_config = ConfigDict(extra="forbid") + + def as_patch_item_info(self) -> PatchItemInfo: + return PatchItemInfo(name=self.name, price=self.price, deleted=None) diff --git a/hw2/hw/shop_api/api/shop/routes.py b/hw2/hw/shop_api/api/shop/routes.py new file mode 100644 index 00000000..e5c0b96e --- /dev/null +++ b/hw2/hw/shop_api/api/shop/routes.py @@ -0,0 +1,286 @@ +from http import HTTPStatus +from typing import Annotated + +from fastapi import APIRouter, HTTPException, Query, Response +from pydantic import Field + +from ... import data + +from .contracts import ( + CartResponse, + ItemResponse, + ItemRequest, + PatchItemRequest, +) + +cart_router = APIRouter(prefix="/cart") +item_router = APIRouter(prefix="/item") + + +@cart_router.post( + "/", + status_code=HTTPStatus.CREATED, +) +async def post_cart(response: Response) -> CartResponse: + """Creates new cart""" + + entity = data.cart_queries.add(data.CartInfo(items=[], price=0.0)) + response.headers["location"] = f"/cart/{entity.id}" + return CartResponse.from_entity(entity) + + +@cart_router.get( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested cart", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested cart as one was not found", + }, + }, +) +async def get_cart_by_id(id: int) -> CartResponse: + """Returns cart by id""" + + entity = data.cart_queries.get_one(id) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /cart/{id} was not found", + ) + + return CartResponse.from_entity(entity) + + +@cart_router.get( + "/", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested carts list by query params", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested carts list by query params", + }, + }, +) +async def get_carts_list( + offset: Annotated[int | None, Field(ge=0), Query(description="Page number")] = 0, + limit: Annotated[int | None, Field(ge=1), Query(description="Page size")] = 10, + min_price: Annotated[ + float | None, Field(ge=0), Query(description="Minimum price") + ] = None, + max_price: Annotated[ + float | None, Field(ge=0), Query(description="Maximum price") + ] = None, + min_quantity: Annotated[ + int | None, Field(ge=0), Query(description="Minimum quantity") + ] = None, + max_quantity: Annotated[ + int | None, Field(ge=0), Query(description="Maximum quantity") + ] = None, +) -> list[CartResponse]: + """Returns carts list by query params""" + + entities = data.cart_queries.get_many( + offset, limit, min_price, max_price, min_quantity, max_quantity + ) + + if not entities: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /cart/ was not found", + ) + + return [CartResponse.from_entity(entity) for entity in entities] + + +@cart_router.post( + "/{cart_id}/add/{item_id}", + status_code=HTTPStatus.CREATED, +) +async def post_item_to_cart( + cart_id: int, item_id: int, response: Response +) -> CartResponse: + """Adds item to cart""" + + entity = data.cart_queries.add_item_to_cart(cart_id, item_id, 1) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Cart {cart_id} or item {item_id} not found", + ) + + response.headers["location"] = f"/cart/{cart_id}" + + return CartResponse.from_entity(entity) + + +@item_router.post( + "/", + status_code=HTTPStatus.CREATED, +) +async def post_item(item: ItemRequest, response: Response) -> ItemResponse: + entity = data.item_queries.add(item.as_item_info()) + response.headers["location"] = f"/item/{entity.id}" + return ItemResponse.from_entity(entity) + + +@item_router.get( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested item", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested item as one was not found", + }, + }, +) +async def get_item_by_id(id: int) -> ItemResponse: + """Returns item by id""" + + entity = data.item_queries.get_one(id) + + if not entity or entity.info.deleted: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /item/{id} was not found", + ) + + return ItemResponse.from_entity(entity) + + +@item_router.get( + "/", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested items list by query params", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested items list by query params", + }, + }, +) +async def get_items_list( + offset: Annotated[int | None, Field(ge=0), Query(description="Page number")] = 0, + limit: Annotated[int | None, Field(ge=1), Query(description="Page size")] = 10, + min_price: Annotated[ + float | None, Field(ge=0), Query(description="Minimum price") + ] = None, + max_price: Annotated[ + float | None, Field(ge=0), Query(description="Maximum price") + ] = None, + show_deleted: Annotated[ + bool | None, Query(description="Show deleted items") + ] = False, +) -> list[ItemResponse]: + """Returns items list by query params""" + + entities = data.item_queries.get_many( + offset, limit, min_price, max_price, show_deleted + ) + + if not entities: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /item/ was not found", + ) + + return [ItemResponse.from_entity(entity) for entity in entities] + + +@item_router.put( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully updated or upserted item", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify item as one was not found", + }, + }, +) +async def put_item( + id: int, + info: ItemRequest, + upsert: Annotated[bool, Query()] = False, +) -> ItemResponse: + """Updates or upserts item by id""" + + existing = data.item_queries.get_one(id) + if not upsert and existing is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + entity = ( + data.item_queries.upsert(id, info.as_item_info()) + if upsert + else data.item_queries.update(id, info.as_item_info()) + ) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + return ItemResponse.from_entity(entity) + + +@item_router.patch( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully patched item", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify item as one was not found", + }, + }, +) +async def patch_item(id: int, info: PatchItemRequest) -> ItemResponse: + """Patches item by id""" + + existing = data.item_queries.get_one(id) + if existing.info.deleted: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + entity = data.item_queries.patch(id, info.as_patch_item_info()) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + return ItemResponse.from_entity(entity) + + +@item_router.delete( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Item successfully deleted (marked as deleted)", + }, + }, +) +async def delete_item(id: int) -> ItemResponse: + """Deletes item by id""" + + entity = data.item_queries.delete(id) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Item {id} not found", + ) + + return ItemResponse.from_entity(entity) diff --git a/hw2/hw/shop_api/data/__init__.py b/hw2/hw/shop_api/data/__init__.py new file mode 100644 index 00000000..53184500 --- /dev/null +++ b/hw2/hw/shop_api/data/__init__.py @@ -0,0 +1,24 @@ +from .models import ( + CartItemInfo, + CartInfo, + CartEntity, + PatchCartInfo, + ItemInfo, + ItemEntity, + PatchItemInfo +) + +from . import item_queries +from . import cart_queries + +__all__ = [ + "CartItemInfo", + "CartInfo", + "CartEntity", + "PatchCartInfo", + "ItemInfo", + "ItemEntity", + "PatchItemInfo", + "item_queries", + "cart_queries", +] diff --git a/hw2/hw/shop_api/data/cart_queries.py b/hw2/hw/shop_api/data/cart_queries.py new file mode 100644 index 00000000..315eca26 --- /dev/null +++ b/hw2/hw/shop_api/data/cart_queries.py @@ -0,0 +1,148 @@ +from typing import Iterable + +from .models import ( + CartInfo, + CartItemInfo, + CartEntity, + PatchCartInfo, +) + +from . import item_queries + +_data = dict[int, CartInfo]() + + +def int_id_generator() -> Iterable[int]: + i = 0 + while True: + yield i + i += 1 + + +_id_generator = int_id_generator() + + +def add(info: CartInfo) -> CartEntity: + _id = next(_id_generator) + _data[_id] = info + + return CartEntity(_id, info) + + +def delete(id: int) -> None: + if id in _data: + del _data[id] + + +def get_one(id: int) -> CartEntity | None: + if id not in _data: + return None + + return CartEntity(id=id, info=_data[id]) + + +def get_many( + offset: int = 0, + limit: int = 10, + min_price: float | None = None, + max_price: float | None = None, + min_quantity: int | None = None, + max_quantity: int | None = None, +) -> Iterable[CartEntity]: + curr = 0 + yielded = 0 + + for id, info in _data.items(): + if min_price is not None and info.price < min_price: + continue + if max_price is not None and info.price > max_price: + continue + + total_quantity = sum(item.quantity for item in info.items) + if min_quantity is not None and total_quantity < min_quantity: + continue + if max_quantity is not None and total_quantity > max_quantity: + continue + + if curr >= offset: + yield CartEntity(id, info) + yielded += 1 + if yielded >= limit: + break + + curr += 1 + + +def update(id: int, info: CartInfo) -> CartEntity | None: + if id not in _data: + return None + + _data[id] = info + + return CartEntity(id=id, info=info) + + +def upsert(id: int, info: CartInfo) -> CartEntity: + _data[id] = info + + return CartEntity(id=id, info=info) + + +def patch(id: int, patch_info: PatchCartInfo) -> CartEntity | None: + if id not in _data: + return None + + if patch_info.items is not None: + _data[id].items = patch_info.items + + return CartEntity(id=id, info=_data[id]) + + +def _calculate_price(cart_info: CartInfo) -> float: + total = 0.0 + for item in cart_info.items: + product = item_queries.get_one(item.id) + if product: + total += product.info.price * item.quantity + return total + + +def add_item_to_cart(cart_id: int, product_id: int, quantity: int) -> CartEntity | None: + if cart_id not in _data: + return None + + product = item_queries.get_one(product_id) + + if not product: + return None + + for item in _data[cart_id].items: + if item.id == product_id: + item.quantity += quantity + _data[cart_id].price = _calculate_price(_data[cart_id]) + return CartEntity(id=cart_id, info=_data[cart_id]) + + cart_item = CartItemInfo( + id=product.id, + name=product.info.name, + quantity=quantity, + available=not product.info.deleted, + ) + + _data[cart_id].items.append(cart_item) + _data[cart_id].price = _calculate_price(_data[cart_id]) + + return CartEntity(id=cart_id, info=_data[cart_id]) + + +def remove_item_from_cart(cart_id: int, product_id: int) -> CartEntity | None: + if cart_id not in _data: + return None + + for item in _data[cart_id].items: + if item.id == product_id: + _data[cart_id].items.remove(item) + _data[cart_id].price = _calculate_price(_data[cart_id]) + return CartEntity(id=cart_id, info=_data[cart_id]) + + return None diff --git a/hw2/hw/shop_api/data/item_queries.py b/hw2/hw/shop_api/data/item_queries.py new file mode 100644 index 00000000..f01f49f0 --- /dev/null +++ b/hw2/hw/shop_api/data/item_queries.py @@ -0,0 +1,96 @@ +from typing import Iterable + +from .models import ( + ItemInfo, + ItemEntity, + PatchItemInfo, +) + +_data = dict[int, ItemInfo]() + + +def int_id_generator() -> Iterable[int]: + i = 0 + while True: + yield i + i += 1 + + +_id_generator = int_id_generator() + + +def add(info: ItemInfo) -> ItemEntity: + _id = next(_id_generator) + _data[_id] = info + + return ItemEntity(_id, info) + + +def delete(id: int) -> ItemEntity | None: + if id not in _data: + return None + + _data[id].deleted = True + return ItemEntity(id=id, info=_data[id]) + + +def get_one(id: int) -> ItemEntity | None: + if id not in _data: + return None + + return ItemEntity(id=id, info=_data[id]) + + +def get_many( + offset: int = 0, + limit: int = 10, + min_price: float | None = None, + max_price: float | None = None, + show_deleted: bool = False, +) -> Iterable[ItemEntity]: + curr = 0 + for id, info in _data.items(): + if not show_deleted and info.deleted: + continue + + if min_price is not None and info.price < min_price: + continue + + if max_price is not None and info.price > max_price: + continue + + if offset <= curr < offset + limit: + yield ItemEntity(id, info) + + curr += 1 + + +def update(id: int, info: ItemInfo) -> ItemEntity | None: + if id not in _data: + return None + + _data[id] = info + + return ItemEntity(id=id, info=info) + + +def upsert(id: int, info: ItemInfo) -> ItemEntity: + _data[id] = info + + return ItemEntity(id=id, info=info) + + +def patch(id: int, patch_info: PatchItemInfo) -> ItemEntity | None: + if id not in _data: + return None + + if patch_info.name is not None: + _data[id].name = patch_info.name + + if patch_info.price is not None: + _data[id].price = patch_info.price + + if patch_info.deleted is not None: + _data[id].deleted = patch_info.deleted + + return ItemEntity(id=id, info=_data[id]) diff --git a/hw2/hw/shop_api/data/models.py b/hw2/hw/shop_api/data/models.py new file mode 100644 index 00000000..5cb6843a --- /dev/null +++ b/hw2/hw/shop_api/data/models.py @@ -0,0 +1,46 @@ +from dataclasses import dataclass + + +@dataclass(slots=True) +class CartItemInfo: + id: int + name: str + quantity: int + available: bool + + +@dataclass(slots=True) +class CartInfo: + items: list[CartItemInfo] + price: float + + +@dataclass(slots=True) +class CartEntity: + id: int + info: CartInfo + + +@dataclass(slots=True) +class PatchCartInfo: + items: list[CartItemInfo] | None = None + + +@dataclass(slots=True) +class ItemInfo: + name: str + price: float + deleted: bool + + +@dataclass(slots=True) +class ItemEntity: + id: int + info: ItemInfo + + +@dataclass(slots=True) +class PatchItemInfo: + name: str | None = None + price: float | None = None + deleted: bool | None = None diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index f60a8c60..51c67869 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,3 +1,7 @@ from fastapi import FastAPI +from .api.shop import cart_router, item_router app = FastAPI(title="Shop API") + +app.include_router(cart_router) +app.include_router(item_router) From ebd8cc811b6b14f72843c32ec5adf549c666cd67 Mon Sep 17 00:00:00 2001 From: JuliaJu Date: Sat, 4 Oct 2025 20:03:48 +0300 Subject: [PATCH 03/13] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 852216e6..c97e57f5 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ dmypy.json # macOS .DS_Store + +# Custom +explain.md \ No newline at end of file From b9bad0d09f21a8553ec2b6d798fa2eceeb738d22 Mon Sep 17 00:00:00 2001 From: JuliaJu Date: Sat, 4 Oct 2025 20:15:30 +0300 Subject: [PATCH 04/13] Add README.md --- hw2/hw/shop_api/README.md | 354 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 hw2/hw/shop_api/README.md diff --git a/hw2/hw/shop_api/README.md b/hw2/hw/shop_api/README.md new file mode 100644 index 00000000..f72b40cd --- /dev/null +++ b/hw2/hw/shop_api/README.md @@ -0,0 +1,354 @@ +# Shop API + +REST API для управления интернет-магазином с поддержкой товаров и корзин покупателей. + +## Возможности + +- 🛍️ Управление товарами (CRUD операции) +- 🛒 Управление корзинами покупателей +- 📊 Фильтрация и пагинация +- 🗑️ Мягкое удаление товаров +- 📍 REST-совместимые эндпоинты с правильными HTTP статусами + +## Технологии + +- **FastAPI** - современный веб-фреймворк для создания API +- **Python 3.10+** - с поддержкой type hints +- **Uvicorn** - ASGI сервер +- **Pydantic** - валидация данных + +## Установка + +```bash +# Установка зависимостей +pip install -r requirements.txt +``` + +## Запуск + +```bash +# Запуск сервера +uvicorn shop_api.main:app --reload + +# Сервер будет доступен по адресу http://localhost:8000 +``` + +## Документация API + +После запуска сервера документация доступна по адресам: +- **Swagger UI**: http://localhost:8000/docs +- **ReDoc**: http://localhost:8000/redoc + +## API Endpoints + +### 📦 Items (Товары) + +#### Создать товар +```http +POST /item/ +Content-Type: application/json + +{ + "name": "iPhone 15", + "price": 79990.0 +} + +Response: 201 Created +Location: /item/{id} +{ + "id": 1, + "name": "iPhone 15", + "price": 79990.0, + "deleted": false +} +``` + +#### Получить товар по ID +```http +GET /item/{id} + +Response: 200 OK +{ + "id": 1, + "name": "iPhone 15", + "price": 79990.0, + "deleted": false +} +``` + +#### Получить список товаров +```http +GET /item/?offset=0&limit=10&min_price=1000&max_price=100000&show_deleted=false + +Response: 200 OK +[ + { + "id": 1, + "name": "iPhone 15", + "price": 79990.0, + "deleted": false + } +] +``` + +**Query параметры:** +- `offset` (int, >=0, default: 0) - номер страницы +- `limit` (int, >=1, default: 10) - размер страницы +- `min_price` (float, >=0, optional) - минимальная цена +- `max_price` (float, >=0, optional) - максимальная цена +- `show_deleted` (bool, default: false) - показывать удаленные товары + +#### Обновить товар (полностью) +```http +PUT /item/{id}?upsert=false +Content-Type: application/json + +{ + "name": "iPhone 15 Pro", + "price": 99990.0 +} + +Response: 200 OK +{ + "id": 1, + "name": "iPhone 15 Pro", + "price": 99990.0, + "deleted": false +} +``` + +#### Обновить товар (частично) +```http +PATCH /item/{id} +Content-Type: application/json + +{ + "price": 89990.0 +} + +Response: 200 OK +{ + "id": 1, + "name": "iPhone 15 Pro", + "price": 89990.0, + "deleted": false +} +``` + +#### Удалить товар +```http +DELETE /item/{id} + +Response: 200 OK +{ + "id": 1, + "name": "iPhone 15 Pro", + "price": 89990.0, + "deleted": true +} +``` + +> ⚠️ Товары удаляются мягко - помечаются флагом `deleted=true` + +### 🛒 Cart (Корзины) + +#### Создать корзину +```http +POST /cart/ + +Response: 201 Created +Location: /cart/{id} +{ + "id": 1, + "items": [], + "price": 0.0 +} +``` + +#### Получить корзину по ID +```http +GET /cart/{id} + +Response: 200 OK +{ + "id": 1, + "items": [ + { + "id": 1, + "name": "iPhone 15", + "quantity": 2, + "available": true + } + ], + "price": 159980.0 +} +``` + +#### Получить список корзин +```http +GET /cart/?offset=0&limit=10&min_price=1000&max_price=500000&min_quantity=1&max_quantity=10 + +Response: 200 OK +[ + { + "id": 1, + "items": [...], + "price": 159980.0 + } +] +``` + +**Query параметры:** +- `offset` (int, >=0, default: 0) - номер страницы +- `limit` (int, >=1, default: 10) - размер страницы +- `min_price` (float, >=0, optional) - минимальная цена корзины +- `max_price` (float, >=0, optional) - максимальная цена корзины +- `min_quantity` (int, >=0, optional) - минимальное количество товаров +- `max_quantity` (int, >=0, optional) - максимальное количество товаров + +#### Добавить товар в корзину +```http +POST /cart/{cart_id}/add/{item_id} + +Response: 201 Created +Location: /cart/{cart_id} +{ + "id": 1, + "items": [ + { + "id": 1, + "name": "iPhone 15", + "quantity": 1, + "available": true + } + ], + "price": 79990.0 +} +``` + +## Модели данных + +### ItemResponse +```json +{ + "id": 1, + "name": "string", + "price": 0.0, + "deleted": false +} +``` + +### ItemRequest +```json +{ + "name": "string", + "price": 0.0 +} +``` + +### PatchItemRequest +```json +{ + "name": "string", // optional + "price": 0.0 // optional +} +``` + +### CartResponse +```json +{ + "id": 1, + "items": [ + { + "id": 1, + "name": "string", + "quantity": 1, + "available": true + } + ], + "price": 0.0 +} +``` + +### CartItemInfo +```json +{ + "id": 1, + "name": "string", + "quantity": 1, + "available": true +} +``` + +## Коды ответов HTTP + +| Код | Описание | +|-----|----------| +| 200 | OK - Успешный запрос | +| 201 | Created - Ресурс успешно создан | +| 304 | Not Modified - Ресурс не был изменен | +| 404 | Not Found - Ресурс не найден | +| 422 | Unprocessable Entity - Ошибка валидации | + +## Примеры использования + +### Python (httpx) +```python +import httpx + +# Создание товара +async with httpx.AsyncClient() as client: + response = await client.post( + "http://localhost:8000/item/", + json={"name": "MacBook Pro", "price": 199990.0} + ) + item = response.json() + print(f"Created item: {item['id']}") + + # Получение товара + response = await client.get(f"http://localhost:8000/item/{item['id']}") + print(response.json()) +``` + +### cURL +```bash +# Создание товара +curl -X POST "http://localhost:8000/item/" \ + -H "Content-Type: application/json" \ + -d '{"name": "MacBook Pro", "price": 199990.0}' + +# Получение списка товаров +curl "http://localhost:8000/item/?offset=0&limit=10" + +# Создание корзины +curl -X POST "http://localhost:8000/cart/" + +# Добавление товара в корзину +curl -X POST "http://localhost:8000/cart/1/add/1" +``` + +## Структура проекта + +``` +shop_api/ +├── __init__.py +├── main.py # Точка входа приложения +├── api/ +│ ├── __init__.py +│ └── shop/ +│ ├── __init__.py +│ ├── routes.py # HTTP эндпоинты +│ └── contracts.py # Pydantic модели запросов/ответов +└── data/ + ├── __init__.py + ├── models.py # Доменные модели + ├── item_queries.py # Работа с товарами + └── cart_queries.py # Работа с корзинами +``` + +## Тестирование + +```bash +# Запуск тестов +pytest test_homework2.py -v +``` From 1d7cdf3226e8be98ac1abb3750649f90a2cd62dc Mon Sep 17 00:00:00 2001 From: JuliaJu Date: Sat, 4 Oct 2025 22:58:14 +0300 Subject: [PATCH 05/13] Implementation WebSocket Chat --- hw2/hw/chat/README.md | 91 +++++++++++++++++++++++++++++++++++++++++ hw2/hw/chat/__init__.py | 0 hw2/hw/chat/client.py | 39 ++++++++++++++++++ hw2/hw/chat/server.py | 74 +++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 hw2/hw/chat/README.md create mode 100644 hw2/hw/chat/__init__.py create mode 100644 hw2/hw/chat/client.py create mode 100644 hw2/hw/chat/server.py diff --git a/hw2/hw/chat/README.md b/hw2/hw/chat/README.md new file mode 100644 index 00000000..e6ab9cf0 --- /dev/null +++ b/hw2/hw/chat/README.md @@ -0,0 +1,91 @@ +# 💬 WebSocket Chat Application + +Многопользовательское чат-приложение на WebSocket с поддержкой нескольких чат-комнат. + +## ✨ Особенности + +- 🚀 Мгновенный обмен сообщениями в реальном времени +- 🏠 Поддержка множественных чат-комнат +- 👤 Автоматическая генерация уникальных имен пользователей +- 🔄 Уведомления о подключении/отключении участников +- 💾 Хранение активных соединений в памяти (stateful архитектура) + +## 🛠 Технологии + +- **Backend**: FastAPI + WebSocket +- **Client**: Python websocket-client + threading + +## 📋 Требования + +```bash +pip install fastapi uvicorn websocket-client +``` + +## 🚀 Быстрый старт + +### 1. Запуск сервера + +```bash +uvicorn server:app --reload +``` + +Сервер запустится на `http://localhost:8000` + +### 2. Запуск клиента + +```bash +# Подключиться к комнате по умолчанию +python client.py + +# Подключиться к конкретной комнате +python client.py room_name +``` + +### 3. Общение + +Просто вводите сообщения и нажимайте Enter. Сообщения будут видны всем участникам комнаты. + +## 📝 Примеры использования + +```bash +# Терминал 1: запуск сервера +uvicorn server:app --reload + +# Терминал 2: первый пользователь в комнате "griffindor" +python client.py griffindor + +# Терминал 3: второй пользователь в той же комнате +python client.py griffindor + +# Терминал 4: пользователь в другой комнате "slytherin" +python client.py slytherin +``` + +## 🏗 Архитектура + +### Server (server.py) + +- **ChatRoom** - класс для управления подписчиками и рассылкой сообщений +- **WebSocket endpoint** `/chat/{chat_name}` - точка подключения клиентов к конкретной комнате +- Автоматическое создание комнат при первом подключении +- Генерация случайных имен пользователей в формате `user_xxxxxx##` + +### Client (client.py) + +- Многопоточная архитектура (отдельный поток для приема сообщений) +- Валидация названий комнат (только буквы, цифры, `-`, `_`) +- Интерактивный ввод сообщений +- Graceful shutdown при `Ctrl+C` + +## ⚠️ Ограничения + +- Соединения хранятся в оперативной памяти процесса +- При перезапуске сервера все соединения теряются + +## 🔧 Формат сообщений + +Все сообщения отображаются в формате: +``` +username :: message text +``` + diff --git a/hw2/hw/chat/__init__.py b/hw2/hw/chat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/chat/client.py b/hw2/hw/chat/client.py new file mode 100644 index 00000000..e7bde71a --- /dev/null +++ b/hw2/hw/chat/client.py @@ -0,0 +1,39 @@ +from websocket import create_connection +import sys +import threading +import re + +chat_name = sys.argv[1] if len(sys.argv) > 1 else "default" + +if not re.fullmatch(r"[a-zA-Z0-9_-]+", chat_name): + print("Error: Сhat name can only contain letters, digits, '-', and '_'") + sys.exit(1) + +ws = create_connection(f"ws://localhost:8000/chat/{chat_name}") + +print(f"Connected to chat: {chat_name}") +print("Type message and press Enter to send. Ctrl+C to exit.\n") + + +def receive_messages(): + """Gets messages from the server and prints them to the console""" + try: + while True: + message = ws.recv() + print(f"\r{message}") + print("> ", end="", flush=True) + except KeyboardInterrupt: + ws.close() + + +receiver_thread = threading.Thread(target=receive_messages, daemon=True) +receiver_thread.start() + +try: + while True: + user_input = input("> ") + if user_input: + ws.send(user_input) +except KeyboardInterrupt: + ws.close() + print("\nDisconnected") diff --git a/hw2/hw/chat/server.py b/hw2/hw/chat/server.py new file mode 100644 index 00000000..0b396535 --- /dev/null +++ b/hw2/hw/chat/server.py @@ -0,0 +1,74 @@ +from dataclasses import dataclass, field +import random +import string + + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect + +app = FastAPI() + + +@dataclass(slots=True) +class ChatRoom: + """Class representing a chat room.""" + + subscribers: dict[WebSocket, str] = field(init=False, default_factory=dict) + + async def subscribe(self, ws: WebSocket) -> str: + """Subscribes a new client to the chat room.""" + + await ws.accept() + random_letters = "".join(random.choices(string.ascii_lowercase, k=6)) + random_numbers = random.randint(10, 99) + username = f"user_{random_letters+str(random_numbers)}" + self.subscribers[ws] = username + return username + + async def unsubscribe(self, ws: WebSocket) -> None: + """Unsubscribes a client from the chat room.""" + + if ws in self.subscribers: + del self.subscribers[ws] + + async def broadcast( + self, message: str, sender_ws: WebSocket, username: str | None = None + ) -> None: + """Broadcasts a message to all clients in the chat room.""" + + if username is None: + username = self.subscribers.get(sender_ws, "unknown") + formatted_message = f"{username} :: {message}" + + for ws in self.subscribers: + await ws.send_text(formatted_message) + + +# Dictionary to store chat rooms by their names +chat_rooms: dict[str, ChatRoom] = {} + + +def get_or_create_room(chat_name: str) -> ChatRoom: + """Returns an existing chat room or creates a new one.""" + + if chat_name not in chat_rooms: + chat_rooms[chat_name] = ChatRoom() + return chat_rooms[chat_name] + + +@app.websocket("/chat/{chat_name}") +async def ws_chat(ws: WebSocket, chat_name: str): + """Handles WebSocket connections for the chat application.""" + + room = get_or_create_room(chat_name) + username = await room.subscribe(ws) + + try: + while True: + text = await ws.receive_text() + await room.broadcast(text, ws) + except WebSocketDisconnect: + await room.unsubscribe(ws) + + # Send system message about user leaving + for subscriber_ws in room.subscribers: + await subscriber_ws.send_text(f"{username} left the chat") From 6a5ff3af73776b38885dc5a9fe5fb3fe4550a362 Mon Sep 17 00:00:00 2001 From: JuliaJu Date: Tue, 7 Oct 2025 20:31:36 +0300 Subject: [PATCH 06/13] Implementation Docker - Prometheus - Grafana --- hw2/hw/Dockerfile | 27 + hw2/hw/assets/availability.png | Bin 0 -> 12088 bytes hw2/hw/assets/cpu_usage.png | Bin 0 -> 25566 bytes hw2/hw/assets/error_rate_4xx.png | Bin 0 -> 21543 bytes hw2/hw/assets/https_status_codes.png | Bin 0 -> 30268 bytes hw2/hw/assets/latency.png | Bin 0 -> 32238 bytes hw2/hw/assets/process_uptime.png | Bin 0 -> 19191 bytes hw2/hw/assets/ram_usage.png | Bin 0 -> 33841 bytes hw2/hw/assets/rps.png | Bin 0 -> 40251 bytes hw2/hw/assets/throughput.png | Bin 0 -> 44175 bytes hw2/hw/docker-compose.yml | 31 + hw2/hw/generate_errors.py | 209 ++++ hw2/hw/grafana-dashboard.json | 1074 +++++++++++++++++++++ hw2/hw/requirements.txt | 1 + hw2/hw/settings/prometheus/prometheus.yml | 10 + hw2/hw/shop_api/README.md | 189 +++- hw2/hw/shop_api/api/shop/routes.py | 8 + hw2/hw/shop_api/main.py | 4 + 18 files changed, 1538 insertions(+), 15 deletions(-) create mode 100644 hw2/hw/Dockerfile create mode 100644 hw2/hw/assets/availability.png create mode 100644 hw2/hw/assets/cpu_usage.png create mode 100644 hw2/hw/assets/error_rate_4xx.png create mode 100644 hw2/hw/assets/https_status_codes.png create mode 100644 hw2/hw/assets/latency.png create mode 100644 hw2/hw/assets/process_uptime.png create mode 100644 hw2/hw/assets/ram_usage.png create mode 100644 hw2/hw/assets/rps.png create mode 100644 hw2/hw/assets/throughput.png create mode 100644 hw2/hw/docker-compose.yml create mode 100644 hw2/hw/generate_errors.py create mode 100644 hw2/hw/grafana-dashboard.json create mode 100644 hw2/hw/settings/prometheus/prometheus.yml diff --git a/hw2/hw/Dockerfile b/hw2/hw/Dockerfile new file mode 100644 index 00000000..f258cff2 --- /dev/null +++ b/hw2/hw/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.13.7 AS base + +ARG PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=on \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=500 + +ENV APP_ROOT=/app + +RUN apt-get update && apt-get install -y gcc +RUN python -m pip install --upgrade pip + +WORKDIR $APP_ROOT/src + +ENV VIRTUAL_ENV=$APP_ROOT/src/.venv \ + PATH=$APP_ROOT/src/.venv/bin:$PATH + +COPY requirements.txt ./ +RUN pip install -r requirements.txt + +COPY . ./ + +FROM base AS local + +CMD ["uvicorn", "shop_api.main:app", "--port", "8080", "--host", "0.0.0.0"] diff --git a/hw2/hw/assets/availability.png b/hw2/hw/assets/availability.png new file mode 100644 index 0000000000000000000000000000000000000000..fc1be6e55f71849710c0d0024234456617757f4e GIT binary patch literal 12088 zcmd6NWl&r})9&I6EU>uC;sgSV6C}6<3lQ9bySu~U1PKHPuECw4!JPzm2m}xA?r=Bn zt@`S%x_|D^@5iZg=JeE=Gc!HiPe0vJD$2507-Sd#008TqoRk^>fDj75-UCtLZ}$@R_y&%%>MkuZL`IgZldfY)NzJ5^0Hvgm z0pP;)7_Kx@D+g;B@PvkzwsOnmjeeUKq>YrB5LjfLziDvN^Yf>)*Gg&yT&jc)nV6Xg zt#iy_A;mC7%wx!q_SjYgVP~`cQIwKGpP2d*<|YCHT}F5&A4|r6;YFg&r~fjA={Uqn z3LkC8>px>c!I6>7U1IRh1;NCmq!3UMad8nq$h5Q=|0KW?3mP+GHekkBCR)EjABS>G~VcRW+d|PF!>dnwoWO{HGkZ_*r568&z*T>DyuUyq1+o_}Du})HhIR`@`@k-@k6Cpw!w}9i; z8-zbMKVBlQPmgVy^T`i1J=a?@9D3vdV#E$le~_9_T!ox9Zad8Fdnj+-20eJ#W1KL5 zKTM~p(5WRNe<@I~*M4F7<9UMAY}y_e!8u-tWc#)aX-V{t!R-8Vo`x1}G`D~sa~s;X z>cWwO)#d{Pc;Dh0&NfpDt;@r%>q>Gb>F&n=`%8i_CsQ2FG1U9{Yl3oZZv*hyf~3xue77JKCk~cWj7yj)roPZ{vE#Z`O?Vvw&O!1(J+yv zQM(KHeT3)i#E>XlvO=?)8mq9`Uaedp};AcM0`?rsH;ghiP7qj|U(_gVmHU0-g z6=_w1vP!> zN}D^ekFRgd-whoAseaSNf!FTwa`V+U5$zxOXzhWGXg4CagqQ5VfPBmY-=@4KySjcU zs@{3G*T;8*{fV9MkKK|vt6}aZM*}T5Cp{~HkAfZs8NcRT=0+kgB6>_I-%>rB+IKu1G@m3c z)i}8G&)Kg=aIAKqw7xte93E>ZDIUD|Q6G2Ktu>qA`gGnxk01Uj+jVqc+WL5y%rAf5 zunil`j-=&268l+kGVmSbv_J7dAnfV3pOpw&O-`;={xp|}cJ^I^0BB7caAnk!Nrd| zKzCK+M-;Oo*JtNL)yJczdv%!bE$G;LmnM8NJb6B)++T5YJ>q5)$ffNu%J>5EdgEZY zi~tgip!TcwqX3XQ%@%f@CWPFYd(HD?d2LjmO{iYAM&KOw&Ii0j9y#i9FYmdbSS=Uz z2g}L;VuT|sadJ?ch{jI>QF~PeuYNA=2kbX^n!xvNZG`(aWo^L}I38dss!S6ymTZej zl@^%~Z5Ojz3_j2@3tESmRsDt?>3JGG_+eVUrfDrhJ9umrd8*q{U+uX+4hHY_)~$-B z1(qD{+b=wYRd41wiogb`mOq~aSWg!SSux#{^cEX-m-b+Z9`(KhRATlVJx#ma=bRm$ z1dV(mSFf8q&!};sf<9(EB@2CWJe08=Wu6N%7?6rBi|75kW zyN#hio6vlzmp=~%FNr#IO8|a*x`hS?Mef__!X`SlP-j7Zo3d`M9V8u3blFA#KRwnZ zyHf|L1JTfwkt(&WjFhGsD}D$g9rPEU9!)<#Z?&Fojh}S2408#%tQq@V9WOZz#yOTP z_DU&jaDL~O75a_|=^*JLmb0lF50kb_nHZh)^Lx zn%dCoRa@}{*yXDa+8|$}TRv|px2hSV#Td)Vcc*Jb0FjG$Wtj!rmET8SL|;*F@?+p? z$D`FPT4c#s5iXEYyR!wq%USq*M`WS7bj{<0n?we6xN3QWSTo}Ewptm{wYBDWyrq`S zbbTvFRB$o8!;fVVf#Ug?@9?P=?d%rzmcrUm(ssR?eB6x6RGfH@(Cw1lFOYZ#27gY;)B^8zrH}EgEhu=km_Yo&5g*Ye7GtmYiDV_V z7eBAx-vL(q%oP)-d$vTRv)T^6dIAKZv;qy?n#fLbPTH{9{3>x|?;n?J>n0YlGyPi^ zjTNO5d9TD$Z*cWJS%OT_&a|D1sBh1>y#l~L^;cpuo`P?FdcqZZ(di0HA%Rp_`%iel zJt!PO|N1$rxvCCdLDRk``=!m;C9#e1j6`!=-fyGY)AXrKS^s9zx5n{;*61%do6V{x zJ=1iT8WGxrnbG9XeP525A$q;Jw+e#P)uU;Rqr_3%V#g62QhqB%8zw?}@<)r#Xc!QP za(o#MxHM&)`*R2FxV~Fn=x|4KXun{+T`cyUz`z_A^>|5LO*Ux94D@|KtvJ+=UTHn< z-|i1`-+j2dIi2|d0c15FU%y@uw)lDImf6>oIG9KCWNRBJYidU&7VzwoeE zy~a(xU?xS-z=GC8s5fT<3BvDu7G=Pwdh$BgvV8bAL@CD2pjLlD!5ASc4q(=qbi(Z42%`Iu8CB3fKoh5cc44Ofb0_PjIiCq;r zN4;aM)cxz+r1jM+K`Sfv3x1m}^>LT$d!4OVIo@`5gIDW=ok$P&%eM!3n0TAt{d*)K1qjYS&Ka_$GjLF0PSM>zq2o8<=b17aP4 z^S-x--e#Gb5>mWOa2#sB9{$yi==E>5u^ei)Kl;>l#KyncKo!&e0-UoSMWY6PvhPaC z-m@uFi0ByucmJC0GHCRSYycW=^31iD>&N^HAJ4|reAMRw5`Jl^-i3F&b z2&~7EB|(*{#j6&ROC7=TT+8-7>1NVwTAz9LbsGQNdjk}TlRaLJ9skoAn7KwnTh`&l zOUe+t>Zj32TAa-|nIt3iOQZBxBpfx*h`mBV%!q-*Wg{9euIhh)Gyn+}8HxB0NF)Bk z*#!S2xc_i={(mHZe>fZE{|ABpul*!LWDpoJ9BM>@swRU`s=hu-;mSSAqsZN6u)GR% zkiiJgqnd_-NP6JCf`Fn3w3ShFjUGD9|H2dnzUYe`G)#Vc>GuG{GANORJ;~Dt!ioJ= zl^a!2l}7+%e}~>|vPtZTuJawy6J~tS$V*IU5?(wWo=V_N(>r(Bd~It72UKArqg>Ip zXqcFD0fvXhDJIakBwzT}_qSMNI7TQ9Jq9A=r7sHz0O>`*N*NL&uA@u5)6sF-Uu z2|nF~VC$j;Wy^s-irlg4L!rh6G8y4yk%Di-_t%t(slM@yf3Bf-jE0fW?s zC_da4Pp1X<(5gdrsGP5(^7Qb9%5phkifu`qW%>IRjLIYeVZQM-}|=V(TFrqW_s z!g?U0ZJ(^jNPs-n62OAKD<%$$o`{g#NQos+_< z?6$Hn3YYm9^K@KoHqFNAE7>!oj29xc=LB5A?_l9U2O*cqAzOY!Q4%jovk(;nl{(3l zIx+8NQdQQTJ>?I<#|j4{%1khU_rVsxT?qBxJrLL3Vy13U$f&nEwkT4xpbtqC1>CB@x0T?OI7^ML=$vr@7 zhi3x~Hzvpg7Cd!8h|ptWP69$y#i5ISU^noG%~yhNpqQfg!f&j2K%uA!C)B4qpc+0g z<9~B!!rb$lhKMr7hQQ*T){7w1W?&WbFjw+mU3#HhawH^3UXOSe8G=$e02F8_Ig!~n zuKJx5j2A90)?h?Z`e;*8lop<_!CX&oLPL)n60|QKt9YLAMU5gkiN}whSBn6d6cu4s zc8DaaHUGX)JBc1)RT>^wpZCggi^m4_BA3R=2<2G*ombrW6}dtJBvVs({~4RwT#sG) zT~;Omhu_aPARiMNtAf+$6y;)XKABn*5}5!>wlSYM{8dh@(7`ZrmNB*>FFdiyMeW0n z6PT-|sjP=Qd`fc|ayX_B!~(TSXrRzkJc+K6c?|6*MiD|j*YDGBp0$Vi;#<_sQTJk2 ztw*p0r!tLixwA(>LRqFUFfd(@Bb8c;sM!UWAsS38y7b2UJ331o36PU!?PI|}wQk*X z>K8@-xi@$$akmaqiUe$LMwCr$2*QSI>l`6(Nr1SoD8&1sXH>qaQjRfNz8M4}K@e@^ z42N)#<}gZXw}djiO$gqgN@T^xjcY1NQP_*C3kdU$Mhi$s!c=7KXlctTPR*hy$F2tH z4AJ1tbO8t`)w;7cjY-+PU#z6o4?jV4Bu+_6Y7<}O-v9#hgh+YepVA`*nu4sRg2T?6 ziPZd|U>o{XDKm>(amMs;BNpO00-Mh;6?0P=+_MV>9{YXWG3&r{#^_4j^@Pg&+>6w2 zk&$KZft~1I>Edm|WgFx_^#(;Gmk`bPq2OBt%&M0$e%04dSy1fTXH$(OWb@8LwXHE| z)YbM;!tbT&C`dx#$5Hb+EkGN`bf{|i@tl*bSMz?kV24lyyI&TXnL!s{CR^XwK>pvH+inI<$SO}_W9S)G*}y3Q>Vy?WZw3k$8}r^)i=__+^jyikB`lC@2UDy1IEBCBX+j`psk~QQ2kWL2*H$uS`j1 zG*!DfG!kQXN*Ss5yifO4*51!9IDw&|3DxiZ+7dthQO`=ki!<$ZCc#Kn$3W)ud9UV_ zrocUlZaGU^c)4_K&|HN%=x!v$$Htn{S;~k&oNa@NAu45uyuQ`KPB9p6ulG}juBHN4 zTNAs{zT2I$xe96A6;fEXH*tv`*src^lp%=PLm$H~ov_^;ZcX-DAj_}YT8GHx2kt@u zpahYdw9x$2hjI{RzV#SiW3E5l665cZd1v4^Jqgn{IMKj6~eu9s9 zhD1}0*wwfuxF7Sw7(){MMH`tn4_CXfOk5x?5hpVI4V6YgM>(S!sw}k<5t1?DO#Piv zgIapbMtTqi!Z6vdd4ODkTF*sBTCl0v!_oS?GM3qB;{v0iX($OsVviV!tTAB{BO^7U zsSRRd;GDuHyz^B?3L7ihVD+0AKGsREnZh?3%ueTveNgZ#l`M4|9^6>Vdd7GhggNB0 zo(tiHIIN|i?(ecf3x#HhQ(iOjrPc*%Z2OGCvZWvh@(4k%k#Ja6;VU2sVgqendUEil zUF{qc(yex>|6t_@3dKWyR4{b0vex-V+4rqia<(A%RHT&XQ+kq}`{D0-m26$Z@H{9L zssy@>yg_`3G4yMaO-wu4$7-4W8b0vrarY;qnl&iNko+BQK$VO>)n`SyAxGE05k=b0 zFF`g$Uk+*F2*=$uL_^FO9H?X)3Lf8z2XrcIsbJJerz{X^E7J*kGP-gEjTm~M$X$-Un@$Iz0>VH`;%~~k*s4v zE`8Fd24kC(r^<*^I8K;sb^?~$BQ|Pb_qPt2MFU(C5KLi-&y=}u`txoc$LerVqUIP^ zQrvo1Oocjpp#9R^SQNH(gRf#5uXbNPvc1+E+g=c91A&q^QZUcd$kJ%H*Q-Y4pnQu6 z_AtN3=V2VbFw!Hk3v|)@Hb*2QndhvBF;M~oUy}3eN9Ok!N5!thDSkS5?=Z|EG-cEs z&w)?YwpW6HB>tBvhd15mM@VLJ$fm&h^2}O6(!Nx8&e-S8&Lhi+*N{ z*?5GbYM(oXhOUbw|jur7oU?6--W&1ha7BNVqEl2KW6Akf1xcE%X;8Ke<2QqA2uo* zZrsJA7S(!aL2DQb4mhHLMODr*3^4?p<1i%dv_xeU8x<&aO4Gh!%8LRb_flJ=^K=)~ z(FdfJI5$@%{xoL~1w%iu6sScp%fRT7B?5ZndVHQ_duhC9?QMi@vL(*hhf8%Czf5;m z__L(<#r>rL2_U6?p|8!0msfUen1dOS$ZW8?7i+#YCs8>^W<4((_i&Nu2BU)LNPCwX za6*&PV$5WoF*m!MNl=p%)qDb&>C0JW{!P{>%FZ51JWO0rRpiko%bT?=jagMa(dxTI zHyoU!rxKK#(;y!ZGC&fLvUhsES4wFVMh%?_&wm7QyvvK9dX^CMK@B#hx3NCR#s8to zZloOg=%miX>1qd(d?|FT$3%u8tI41?`9(DXT&}g@KEjp-2SS%NI(jYdOUqlg7X9u6KPf* zNklQ<%1mgszXu%-KQIUzPgWA$aD`7l5kKpt;w{&j82TL-2EF~*9 z6@@cloH%>iz&S~EoT&@ENjEcKdwQ(N$Dy)q+V*Nsu%ej4kbrHzkJF#rJvmc}YF&wn zoI>3qiJTCfF{`7XNJho7t2j5&gehw-`w9hJ2r}r%7R`pJ$m|2|WI-vqEmiKB7zhFa z8e4Iiy`6*Q`s|rL5wL!F&O|bNim}?)pNEy-(T^n=(-ev;$lB%P!S$ebhE`)Z;rX0E zipL$kkyn?7a}p$|AS`7E(+5x(q+TFstKDYE5c9pg{VM5Htz<=eQN)5Tpr8kSzgn&|x9 z?mz#H4xw>#U^h7mD!D)JRk^BUh)k_^BxqIB5-Y(rSIUSEE_-c@CN)c}&o4f2T;iNl zK-6hz0en4=iz7E4(ra8K#k=XvF07PN|C6I1e;Vb)gcukOmaFv;lQM6vDtE6_&9r16 zEc=U<9_?oEGY_Y-djHiQ?MHOJ+xgj*v2y*bm@TJ=WfG(2V39G9y!ASf^9F|MEl z&wdUK-SJ*z|18qdq1x8BO^P1KrAvV`=-q*C#PRp`x-Em5kP$m3+b!vURRR6C9b>~^H`74KA-j~KT<%%XQW@G*{RX>~Tj9i>HDbW_TZPA~)o1v=vkgN+)wDWHreK zOXXMQ^IVUFlZ*sX!*ZtR^ic@O4JCTlptE zUaC@a=DxhIV1uX%V=M%~4mEhM>jEA$?mpn=vm}JIm=$S0t{3U=r!zkbmzvT@iT-5# zvRYMSv`Yw_%DI$~I%6sfh+ghDW|we-ft_cqzN<%kH>8H<%M>DtOW0_BBaUpOhI|nd z11$~}Ni=a|=gTk_qLuzO@l?H`lOh26%z@o(6X|Wt_8-dTJ^A+YiW887PcvfO@=Sf{ zRli*bQ`!QtNPqzus$QlQ&d&%5hU(P84y$_ZEfmReh>(4c@4rm3U~;niQ|V;0c(m-~ z2UI{^>^I335b&N{Q%7fstkIkZSUtAnxzj_??Lh@sRjf14TxwxZ_vVWZ5Ei*(sQ3v3 ziCs&)fR6J0b|^T(oo+@140y5u3K}+NLwDaA;bpTb9s|-{H{hLaH$@Q?t%0_Y0w!*H@da+zqSr?WHGKSM zX+)wD#4(ElJ&8JBx;fjfTA$MiP-9W@xRu!dmch_+8SVhstE>Inv|i2*;bZ#wm+ZvXCXnpSk$si z+trgX$X#TTYG0iV*Pb1dLr*?6o;swnmHrm^7CH2*1uZzB{+>j&XT)Yaf2Y$Rq})NL zY`H)=J)3#O0kx-@)=MiAgQN&{<5z%h`|S2c()iP*P$hmsk_af&dGAIw|Nc9>pupYK zlGjC8)GF$GY&y@C;2lM~zsXQ!!THxcg!~3%E7Iz`?v&<9@`drr<3Yx|36j}6kQDOu zb3%L-YXapdSyH8&tA-Kl9%_RZ2e$wt1dFj@O}ke$94!K;H{~>4RJDM*xv7BV)1>{Oh_3@hQpqRCZP1(~x&leK3mpv4Hil1R8Us4>D*ULr~ z5*#Ki75)8oFAU;}x+7uGxLtO2@mN{;D7u$xvh2kh?o3~Q<~AY6vHaiKeoC@l+_4IE zCoaf$8uMZ*k^*#I$IR;?Oih%cD@QP&<-!jYR z(uoSfE{el@GY|lKMEECdnJ}pFqJP3S78bY>kmBJaMwL0037eF<(3VW4SmuHtx?>}c z${a;LzC_YG-+NlF>lQ{(HP-XySvS7LY?M}Na&-Bz-J@IPO0z57gac+6U(4uql95O2 ziP7PX|K)PK*SB{OtEr>%B2^MdGxNf$9POd>If?nl1(6J@!nguhV$`VUY>x}_nj_!k z`!g(;FmF}|1vj?PfCL~Wr3I_9r75XxP;FDBwb4&khQ{XN=fO{_K`|FVa<~pt9)ti? zntT4l+WqM{boMEcNPJf*O(nK|eN@VQTG$uWV&v7b^Wh?Uo1kH_uA>iwg!-15zk(3y zI%^iK$PzUR%QjnTm|Gp`3J6fczEHz{>a)6uD`CYeAjMlQ`$2h*7hLd?qpa@!;oEzB zhN0G$?QW<-pi+eLM=6JrOCMQvxjH_&QX;_^iM{u_SbI%fSwVTz5m9)i*N<$ADajq# z-;=sZB`te0N|7}rCw7D=G)QPZi3J5z%INy1GQat1-wYb9R9YVe0BnEu=v9M4`9Gjb z?D7vVm#IY3fD1IEXhH!3cM*?5R0jsS&85b))h18axvoRfu@MhM)u^qi!EnM(r z15XOBPIoiXl@)ukXNB+vwovSC*QOjG1Lm)tF1yGRvZVK=TA`86)eUef+XBw_uKmCu zaGA85cKF&SJ3`+9S?ydwdo1{-ar1sjt2>jkF>UjY?atXuM+gaN z#VYvMF(vE79V`sKP(dBk*NIi6BjnTL#1NhCmHU`^A zaOGnlU*&%*CsHD^1**SSLwp(>-%R6pA$z}a^#RnuNzs9+6yz`}Enm+xjGOF&XBf&N zKvNn;@J1`UY`^=#>cvisZ1mhA0wwx!bsF%zAL-b@xa++tKZfLbE-#Cutm6hdNc zW?@vMz;Qq>tL9TvL80Gtd1X&-P^c>Kj@sNUFA&*ZR2X4C`_^*v@fRkEpDS=GvDwK2 zIga-%r!eYWF*kIyUX#fs&a}6&GCf?PLWUHfOy;lkj9CHjnob?+!j3jRc_dPa?Yl6* z`tW&u!n9iX)?*`u$@%yS0_u1(0xf~%%2au^86v1qZR`9KvmM1Mo03rq(i| zV1W+1K<}QCmy3mD?>=DWdrDHyo{*Odb6XUi3{bL@byS)HHS!ghpj2a!YvD80Jk0{1 zTX%n6I8~k;I;^jb3MjxoN{O=>hh9EZl`V?bo12)yXcz#aj3u>SRSX?|O2E&l-LhL< z)Ei8TF#dT(1zY^@_Jy{<>`jPgM}R*m0Tx6J%0o`dY>2;Pk7!c}mh>7je zTZpOK%L5JE66(#c-TuPhI&m3Cx;`4z`Ph3mP27{gA|48@FQ=p_fk^p%q0reYy2s70 zr24m;TR$2jT$zTNM#qmbL%zN#9A7NAe;)Gu+J5-A{}~t5-Y+J{)eByH&C9)*_9qtx zj=)YTRmlS{8%NhobL6lHO<8IB5-B*aiGb?Uw+;EwPRdls_e29as(L?`KC5GiSD#qT ze2Yd*pyb`7My@2{HF=$v|A8Z?e`1LkZr4lh#1%wqz?GP`^C>i;UCGpj!K8Ka)ddHO zRLYN^=5gf&dnc-Y$w>^DFb9~y0K30G(;(0DzS`;O0K#t1W)Zb1zqF+9L8k z+@F5(tWGCh$@Dyqg|yvmEUk|w(Ma?{0UOgMc-Fs)OhqLoYAZG^tEdd0ejPLms06&e zi6cx|OQ$?3^j6m0oqcd(GrpVd~CnYjn|CRv{zC{EikmX!&*&VKpBik zSMwUP>c9%$MAiH5q1t4v@9uP47!nl6%F#XgQ9pXj9n-j=$U|nn#foU|W%j{o_Uo`h z6(<249eAxLXlV;s0o3H1vk$7#{{}jmQel^QfIshiwQ?W0C0?{(43l2Ia9*}&tu?t&Guz?Qvg^2@h$8NI@NK)_+?&O=7|)54o_9;4 z7)vLwKi%i9?)~MglrMlTb6j##4QDJam|UH8-Ni)siydIe%EbuiF)=Zrp3-%T)({Bj zg>3jrZ;@sskxKZb7jH(J{*oMlkRNm2ZOy-_vSJ|#fBP?m{_eQ(t=pMZ;m8P&#i*@# zOMhtU8i^|ltb>|rkfY;ls=m`MSyL$1gjXv)od!5FD)uV*`~i6o)xRB1S1pyTS~3Nc zXAzNzJXCRvS8M8OVr#O)H)mY{TloDfkOXzw6K*Tc>H>Wb=A@b0%Gq4QqkdEBjCAgQ z$zS?2yaF2;x$NuhO5A-|cYZt%R2Bs>zX`Umnb)w~YP5%>e0U13vYq#jSIA6{21kgA ziO*Iw7GNMOg(s8$Q=lbHXJE~{lKnVpKm3Oq%8COQT;;L;>drf^fV0??wPYPwGBYqD z_t(JpM|>$b7hikel22AI-zTkznCF4k4`vh~dmrqrLVVkj=J$hNA4n8zaC`zmFa~ z^V^qKj%EEv)ki{gUW7qXO3FS7TC!^ta?zK2$FrBWfdHnDKCSz+fAgk^PY<8QH-EJT ze0tv8k`=&>WlE*q@}|o46Zjin=Tbwd*TAd}p$LXXen=y@WBwLo3@lLFk}Xi@T4XPg zK`ZU~=#DqIXM_)R8S9zMX^YbeJQoB_i@{Iz2xHxbT%OBeG&?*R3CR(?)qA|ph~RW; z^FV?K43#9@eM=U`nQ}Ba*XN+dK>R>nOJ8^?OFY{Y4n9zofvLX=Q?wWe^?eA@4LkS| zzhX7T1-P8%5eo5qb|&AhZbq)#vE;N3AS%T}Iq8St!hKjw(>?0xHm0NLX)L5;l_0HW zQXWoV%9Ywe6?rIvm5+3JQ&K)yC#l*Y!dXCgZn;RO<7+P7?5HOq8bX%@EHa;mks`)O z!1m6`$&UQ1{cM3_{ug-|^qPl_Ry>(aQGPbA*ylyo=-~;Wn;t(L0av=R`L|#yL!}JY z*K~&07sDET<&U2O|ofhiSYlM@&>FzFR0Rd@gHX+^Jp-4AKcc%ylh?K|%q(r(KL_j13DFNvc>3Xlt z^E@;2n|a@vS>GSuUa(xa@2k)2jN?4cJ4Rhq9vg!K0|J3yKT&{cLLf-!5D0=Q8VdNI zVFL|U@DGB!ro1$yVuWfF{DJ&TN<|6+sZPSYvbYESMt4y#aECx}y5YYF{mw9J2*k?$ z2~kz+sA0_JIaBPpR>Wjebt>FuezGk1xChXNi@nXng|k%i1f+yN#4(<*LknR z5W{uQvN@zr7F=8EIloV(w@K`vZTEbC$vJnr-{g<5?>>@Z_51Vs)Z@ef>qOkpX!r*f zp+6%Dg@5KLWf1YL1Gvwjf?<>c{ld;$U(*-1m@+?G4956Q$Gh^*a&?d(ds z-p+Dl45g%`#0ZsPlf)5&ALdkYr+Q4&NLHQi- zo|I{x4eYANJ7_3j7RRgah-MrP9Zj?0!^0kbwf$5zJsnjWqx|da;PJDNi9a6N+OIIP z=NL1FT#t1GEr0hsPgX3-KY9P2x#jYP(&sj;9s%;*Ai!l&3|G2%yk3Oz;PP7kbelr- zrl;H1!7hB5BnfrT57{?|=Jba*%gN3>vDekUb0cA)d_wQ|sCc4t`=!W-NZm>+@0r7O zKPQQ;(Zh}ITWLi%yX6pmacBB-(E9P{mrtk=e~~!k zA!X^NNsIFykDrbRVY)Sn?vQ0)UR%tl)_NEIuQ2(=;8i)C?fhgc&H8K%Du9r;E zi^1u7p1R>=EBmWpWzY=&op6Xq_egiaQmpdEY!7$6&GJ8e68cc*I_`UQIZ1kWjr)L( zAg0T%{5$ort($;U*<@$lEvNFQ3|A|bpN+-0332Jhi3r!I%Rla|ujD3)Mm!-bMu70y zVc+;qBb^P+2lf6bso}9()i?GY9`?DS9cAF*5%$QzBryw>r|~7Ic{WHf=I513l+IOi<-eqooziUHdg*#x@LG-M?-(_exC|sYa`j+!bWohAZzb7=}(c#4SE!0VMv1+<$_2+|>j5*x>)>{&iw-TsvB)Ji}dN;x7 zD=b!f2AExmOmKm-Q^UtZCx}57&&B2EpX~a^^L&EhOQxp)SId&7aqS=9AJ@f~=6qRI zFODJ$NyzI_EBld3v_mH*JO92P#}>zVlR&;Z)$GPY<9{a3C&0gd zrmba^09L=cstu9M6>sF;z}9cvlK-$+g#$TQQCqaQOf1iz<{^t87~YNg9n0%~K3R;0 zjCkN9dbYJay6o00wELGNX!l5m4sC`TS4c`r7gTvD%N3$y{(9JTzHSoU=uRS8KJ_yU zcd>KmHc-{aw)xEGJj&L=p@)goB;<7AhN!aT4+>ZFDmz&MbDj-$=LFlM!#I{pY+560|@~R)hup=bjZSY;Vyz#io{MW%cl+7BbUjqlZoseaD|hr9rdkLXsr$X*N0&Z6_GIPa3YRPFjb1M7 zwgd7h{q*TWK0dysrF92~|q+0M+o*S;8adv&_#bDp+-uzgXhUdErSXp_I4 zPDBcH9YfFox9tQ)o=wlY&`%OpuA%qV*+a28*}4r=*M_t>=2aUaDa~C;GYSq_Jg1Q$1HCvxL_}1z@9^k>Z>xU$GN~=Up|!Pjmo2yDO5gEJQ^2E( zW9nc|BLPdGKW1vYNv{HF;dTeE*SgLIzi$4~KOD~R**(rkx_ky-Iz#NMI_Xg0QAzC0 z+uHeFJm2NB<|e~`RtvW&p9sHZ@{+|z*E8i+S0nT?$#(rl|L$T0GyGCd-p6HH89cVb)F`fHeA;=)04y;esNbbKT5|Ydnth>XdLu_(Gneg zhj-@M<$Wmb1UbrF!IowHt-JV$nFszWMH|@grbE}Z^Oc#cFX7z-9N+SCfyUr* z>mhrUo|acq^2F!)UxEZlki+x~2+%jumM1{Lp$g5Hk&`S@k7y&pcmWf)nss>h{+-qC z!!jQ45yaJBf9^r_zmRYk_@isvVV9Pb8JVO+&t~gWP1d6`#NwUk6wFL#UOd-s*NW?n zEhy;rSuQF*{5dqbwE2v-KWb0db$iA+Ax*3+g)bSa`GkzlBhtiV{+`+e!qH$}pSH4>LH<_nEj_Y}>mS0qx! zlj%|v^2RsECMM95`jH@HJpM!# zt-qd%o?fNu*Ldk{=vV8<0|(Ob{jJYh1o$$K z4)e&6*CH=aNH`5cgT#(;9cEkTJscDj*(=&lNIn=|6Q&0>Va1VfNtrsC{W$HI_c`nA zulCxtYq?!EDz8w5?)FkVlhv`Y7C)vd*sy?~PxY+Y90s~ZcHzqDc}nIO;nApR^vyL& zfmwUVYta`XI5eWc*O#XeiQ{&kUQ0OR1512x9QzOfqFPL7S&jU>WD}eqh*@ksZT^z& z_pk_L`7mndwj)}fR#W2_=d}%2-^w)f^x|&Y&VyX|W@P%0KW|~TnkFSl1k!S45JIXP zFP|b`1sZ#8YEDdOTdlk|Gcx%r`Yd3x8&5ENl+AJAGjD4vc9DNV&yiWCzXQtk&i`j1 z`QIz3UOKB%9QIVr8D-$UalxZd%Xn?J-k!Org2gM-uPkZ)PT|(-vxFRS3fKCiGgl1Q1eMJd)KodBw*!*-(AB zI}b)r;TOgZbcT!=3bt_N9vpZv5ya(v9mp48vrw2~u&Kh%w0fB$EKZDuqS01JQG3s8 zb!0Q9kDY~rJqr&DIU*yU)%@>`<1ghS{xcVY;N+$-&fF;^4h*QZK(b|Bp z_PBKU^ejb;32D}cpW<`&lH+fNnZ(oiF7ss3fk+yw)G;d$UPzw5Jik|<7amhK`!cJ2 zXlnz$coOXkcfW!*7=p^=h}U&s^8>gRBQ3jr1!8_xgAZ-_o;r$Qh}WZoUFo=|3`^J$ z$W|PsdTTq)W;a0!uMiEkj*83U7PPah?P3`Yd?}7JN%joM(qzf^FQvgxeu_+fs!WOf zO|djfl(P$~AEOMT9S%x!n-CEOO4oi77@r^$IpFEjyt)pYXXTXU>e1|f zPvld1G97Q`O44OrPS)GR*jP7>5AtSPlz<5L#2=rrC5(Kgtm=g zGdf|P=W7o{)B--rw>SLKXI9}t;yuU zgNv?xZ1a8=)bOI@KPfQ-SuYbm?&{1O_e$|;^U*Ki20b8E>ParicD}JuWWyRX+(@nE zF51eAo9-B2ji?{Q^e!YdEyi77rWI8Y_sKE!$u_+9P9=RS=R8xZrqk~J(Oh}ba`(ZU zy50s}I-B&2UW}#LhZ8LTz=+tu<$yS*hvCiffRICA5H7nn2u3abJTd$u!Gej0U?lz{ zEGx_l8L2bfoGxq@7R;uC3vSDWHK%9-%>>oQ!joib{-~XU=Ibscxda7iB*#yCw*i>>4|9lYz6k3QJjF#0vc!iSNYEzp$?kZ^v-%@5-uk^U|$)`KW)a zEwwo<^ne6Uy_cI!Mz&5=k@^Oqyt;vu;?BA*MM+a#l^{=EZjWj2iY(XO=R(uoDV6e1 zB#*f+hyC!g(@Ni1t*RvGZPVW^b}WBlu0U6?5<4mQse;wh7Ev382}3snP011C3^9K` z$v06YM42g~^#giD`zy;)y?d}310?z9R?0t;QLm-6_bOx+0}(%nCBSC&P;<+!1yu^-|!XIQJES=?$a1VVc*Wj$ViYDw!x%)6qFA{0%p#BsoJhg zZal6eB@$^ozymHPKHSaXk>tSdoSF;&JdTE*6EnBhc`@zHz%>^x{)m@udLTRs57=5T ze}-6uEFpetb~d}_&OM0{3eu@tZx+oX!msZBM|^CcGtRrt=t_d9N>8Tm`=H<|Y%5b0 zCCnWB;`)>II+CXFC0)wlG=SPTmYaD&p9%vt9_J-;(wTZ!;*{cZEt<2&k@;cDXXc;%SYijL#|muBo~IyOTJ!4pfSyy5`&-ND806@ zM@#_8vs2^5nAXcX{eu_EqA(xyj9eWCMK!d)5^>~G+g^aI-_1eFkIdFcrm=}J=rGC?PX6WSRT4yj7P zyam%FB-E46H8-ikG075bi<_$mIGWdXC4=0&?X1GEB|vatsJeZQW-{2B8Yr5#xq4Ayb7YZPwtKl)ZM`&4#D?o;l(QpyT>+j2KE>Noscf5=if zIKZ)?MjI;N0VGT!kXQ4i7Cd%ZI=YOAS~?odusVHfGO(E%HQKI^6E&asz2=w2a8}yP zM^D04w|h{AL@`2*X`4a5uJzyf`CV)ro(~G>s&1ngfLLIZyc|`bW7x7~NEH@mb#UP1 z7o#R7UhDcrsHm#iOY3w^7-&$B;oN#HLc(br%9i?mdC$+u`N;Rbb-GNK+2}2+g&d|C z8Dgp8CS$is*?}2TSUhjdQ&P7YH}VTFa*}5twEC7KQ@xx(&1z`I#xah5cU=`u#sL^4 zNyj_>+wmWalatZ7^$*JGI&Sw+rD$AM7KL&SFA{(bIv$OR#Q?{$Yq=^ak@zo+@agEA zI8w70BqBL9N|g;bOiaBXXj>XArtMv5ZSJmE%+f3g2l6XGsDb>NlVrjLDP#i=p)hEX zHo~4=y}H&L6CDYLd)7)Wg2)%4LAY$ke`irEMIv%c);`!NPItK@Q)!mLF{m`ytJT2VqH8 zyR(x%x83#9{e&8q)qMsUI^>yZVythBIlHflnU}ZQD?-YqI61tP+;XtA_9zmaX-%WD z!j8LmsZsx3cSg@r<*?iwvj%!^*XCD_ir*rqr4hs7!7jv5-NN3h7jfz)S`n3c{^$@F zksQ1@CIuyxC@6!JmDcHx$8VyW>KYsfDauT+(8_(NOo_@XC3DIB8z}HK zu@O~90*haZa#qu%QR;PR5@z`t)3}n8Z6Xf7>oeP9tW^K3xnev)Ro4gs0b5*v&2 zhz?I9%0cA2`4X6d?>q%%{6Oc z=lOwap+K9$3cpd0;c9gYg8o8RdvCd}xSs$Ef*NQS*pLO~!cUKhX8jCz{$?{3IHW-* z#LeUciN&@7_q0*&Ikc+4$B-olsR@n8TG?sp*{}R@I?&I=0*O9w$>wg7<0&;zv*T;{ zS%MMlo`Dg7alF5@Mi`;Cx>QP_@0XdUHi4l8g%Ziawe~It{b<)Q8q1*0y4>cJ;-9x~ z5nQ-KqG9v!>`3kXYG`=F0<-qcvdo0)RA(BK!eCO==Z>f{uu4*3wF|u7#c0XAe^pRX zC8b|r1rxKDmw9pDS5*NYWNKIy?-rq-!LVQ_xLQ3QDlYyFoSgi!r?D{cO`4@!B zn<5r5(a{X?z$b*|+)2+vnXXLO!{@;~!*mYJ3H_|LCH*U=z@SU40uf4j@P+$u`vRK|sMGUK0lF7?P8hV1Q zSSol64Y-d+WCIQpc;ic@-k3@r6gy1V+(qstU`BlK8Oa)agFH~I*TKx*i28enbTdl*N(KEb_zj%~jCiL5&6E5>3Q3 z4SM(qg~LxsR^zidCHv_F@R-q*JhBiMAXIBA+^0)-la6syz>_MRqNAtBkBNB#;4MfX zB@IbjZ0L@gn{C-W1epXzO4S_us0ZSN5J_`={RK?cTB zdU^n@5wfWEu4R}eJb2hl^61%{d)b`g+G@(meFg5MD?c$G_5*p#*DNt&XRv;D-1(`b z#+;i>rXSuZZyHhhTOcXRNB9iGravHB6!lU}46*4TY6Jkd(nEt-;hGUSS(|$}q$K9E zuQD4BLY3&!1d?#RycVD=VcXQd9J2e8Qv+!f4nTmA)Z9}dF+)r<4BN5`M3jf-q?Xr{ zJ2g`#q^VlOjHm}_ASsu;aRTSb2|zPlC8||7;RXLo{+6rHHE>|>@aQw`*Jf{=FU*f1 zlK;Ia4|~(E1kKR`XZN{I2R)k&ai=KZYhMXnno>A`$zp?H5#c{yGW@umZiaUXRtrb@ zigdf3>bqB-2j;0id5-TPLjC(Fs&ms0Dr+MQz+o9vCkg+m9qNe*H=>Ye5tbBaczByD z>!QJZ4h`f6xWK(QU$p^OEXGhlNV-JZa_bd&!_SZQ++JUh;mHrrW6%E$a?*e+fbS_z zQHH^uil}p$wYbmv8%Qg+8(bo_qyICQlBHYL+tRpR@a*Zs*TW&(FQ?1jXx{HJ~>i2IqA`;?`?c+%5#Vp_i>Qf!J$V_k=e9+a_bN&3K zWQ$+;UWp7r)vpN0E&^GFg%)MxD+GtLeJ+aSPW zNS95T03|Esm2+=te;tO>Ji(nMv6k>`GaS>+i4WXVH>oCZL=QC*D)mJ{2&4n>>i1?oDc7=$uLj0vCZrkH0<`12MW9IS* z@MVobtie#Q$50(aWrs7_z3;RznNUy$fPnYfs)FDi^TDkGnj(euidOnuO6w>5Ss~(g zZZTB=4}KO53=yj|qZ_(I>o9DlH~Fin@v)OnRtxugx6eQWGu~xrGx| zCnB-qUTnBcz`rvO2mlSaVB{=4nZi&avH{Kt*~?nI7+JW7k;8m9Wrdw*QYQIG3(UDy z(EoI{f0-Rg5E_5>b}QA8NAp2)c_rBSrjrfi^i#UP*dCFLygWbMzrbb&OXR4FfVP*b zPy!|n&bTOeG~w^gE^U%xqF>-;2{w`~2fh!QaDz0W{yVtgSBV^gMZ~Pqow9{b`L8aS zp^}!sq1ai~N`L(FUpBHh1az>0#rnyOO!E_LraSp-%0C6t050lZ zCQgUtz;{v^Zti1|@8*>JV=*$8`o{$y$&3=kot8v{Lv|u6mL+6$#*aP&o6I~xDtKoU z(SUa`_WT=*EuD^nx)A_0U`U{mxx$n*BMCY%_8zhDwWwabQ zj17K#A}UX=SMKbgrECXfaA6DjFDzAI1_lu=_@axY-oNa6T_Mj@FP^{#&W=;919&Kp z;0U7!#Y@!hU)2}To|PNv7vNX}2j*@MHCpTI}%YX2#xG z>oLG5Ku1T90lLsar&=EiApGy%S=;i>R%L0kM~s<=D>Gx-vw+x4GY$Dr_~|RhGJ-SN^OBbjp_fv4l2!NLj#-2d7;INvN|2oa zga^)+lZzpry@=w2qf%NPA9Hb!reA{2tqZ}vequOOGpHFu_Y&lgiZmn6k4U@rPp!^> zH#{{gXe=d>Z88ZDg!19m~KPMj;>yN*A;aTpp=WDnSNTxDMrZ!4a zKQ?l95*r(5sA{oH;0?EsNdgSU(Vu)}0nf|grniP=874B|X*g6{Qv{pic}93y#8^TD zT#?`hMh>DPz>H$$VhbHw3kN(RCnpK44GA>+sS0V60BXqpA_h-6_y&biGdtQ2Rmf4T z#vA-|&Cf5xqBlI?X^-^L}83jJ1it| zJWe~%FK>lOAGZDG@DLG8H+7{}RE))UR_aJqlZXWb2~0Ii4fZs3;B-D^h=tVjhEh1u zK_J}k?0EjnitC>zIig)8T~0lrc9_8yo@u?}{#@^o%VJpr$LthNw7{Wf3^6%~bM8_R zKoz3ZdWbX3kkS+*NX>jvqA+pMaS;zM>nz2H8 zHCE#GfUU|&Tb`f=2JLC>?}pZ(@jkdVo&r34MwZUHY5z?Ygyr(*gm{AA32};`0F6Gx~s&P*?on$#VtTx6wMI zjwI=XmGFfl~;SVl84L>sk1E%AF^F3v&;czP`Hp#=D$ zH*_Gp4ZVRN-q3}H&>`MPx;IOZ>Efb5K$KRZiIJ&^7@><2P(adzjE%)+xZz+ok-=B6 zSB2ymfdhPE#*-R`=aN*4^}5SnO9_NNQ!an>3tv2O1sRutT3*&;H+?^flw|u5{yF5z zaa6Tb+_xyW{;Cq9PP8p5ramTtXKze>asqu!#LtX}`(NaUH&BWm2wdP{(Ogbl_*{P~ z-!H2B8bX~*jYmV_>Zl?JELy>e$O5IIH_u^RKI@!mLZ}W?7eQ~;gax_+WqNSsZIhh- z*2@_^wr}q+&;h1w&Hj97`0_vGGSUbJ_=j^p4NZO{T(I?f6L@Cm^y&Joq&uC$H_6-| z1Q@ZD8hRU&Ci&E0eP?3w5<z4!-2jn`)dcYyfZ@}W=TQ;(k^1-*qZOj0wT{Nn%rP#T0>Ml zlR0}BtH5UJCo=bONsi{j$7ii3L5FVM`(+-#dNf@(ewhJtp8PIC+yGOn6sO4t8(2M` z>=qY6fklX4cA|gmUPOI~Fa$a2hjG0Zf#57}8rI1-!l+QI>OZ^%>t{r)*lt8Onvn$w z#I~hgo!c1g8X09a>Kkj^?`>0NMltNu$xkd%V+C~z{4Y1fR+UR%b);KKNx!`p$%tKw z`(2~9m_+Atd+GU~uKA^ib{nLKsp~)1ZKSz#sA`lJ_rC#uW#g6SDP>Z;#*+;|mXQ#v z2CxnpOL{E#(Z1C|x7BuYukXvf1-;FQ7-+#Sjga(gj|2mBttOfaBF^|h!v<=QiTPCc zE14Q-wzK(a+B(8ZqZDBQa=aK97aN|>-V+@*-ZCJcmY;gRZSAS4l}5gY5pk@bc3ytL z6Q9iS_q=R)i_@{;#*bFn$=OdQ3*6K=L4t0{=|H-Yr(;Yz=-VPDFp9k&_5xRUPTqII z9(tKC3PyAhcp_etK9yla(QU7i)!%Y!@-r*ax}P3vF~Z%E{7qETTwzQKfK>1EXln1_ zOwg3dYt<_G7#7o|$0m#LA&}os{i?k!y*b4W<7Dqy&su(dP~f9=Pf<`WLVP}nL?)Kx z@C8ZFy8e>qxq59qP6!JAJGrqZK@Wj@MpV;uln@0{wBM7|I+E0!BxMX%#h(|PSSe3i zOzAY$)pUW;9Lr*Nb&K$4D#pf-Jy9U1 zN&-U1I;)*H8|JlavCB}AgbuO~J*U+L{?CrVh}7f+jO=MA3soctcg&HEhp*`hX5LM~ z0A)bTIXU&h$YR^EtKuOJy3)jxhAlCLktEbmDec`d!t~}>dq2Rj4QOj(wSIUA)@C^; zf2rX;gK)~WhcQ8p?2j-(1?$gGV*+`J@%-)2S*0M2Xb{#!t-?B^?*DC3se?9n(fhak zBh?JK!id&3@ec{3Pk{3p`6?fsb#?$1=IMnR#3lXfwY7rvRhm39ezE`uzFjKfWe^96 zZjAI<;uGiuN{n$R$X2~_TKkhQQekbyG@(oUg&f5q1~#XW{c0dPJBB)!M)a4c55pZS zIQ;Ut$tZsV@_y$0Wkz8Afx3Z_@A63ogvhRBhTzO5Bre_ek9xrZCK&i7 zX!udLmOU^2`A!6Ur*JpPl$W_3CJ9Y3ce|F1`MSiO*Jh&;j7SDX#D_E*Vz|FYuz6e> z!L$`>he>7o!Oab9fbiW;EnC`%xWnTWi01E=Wmd=VvJp-W4N^~A9Uf9{t!X#w>W>D4 zy`Me$W_NQmm{U8gd1Wdu9rIuROM36{@AO_K$A_F~)}K(x*#jt%^9MX#80HFCy&YIR zLy3traHIQ!5yGlBelWcjv3Uv;%8dC#$KaYN;MtMtRn-v+?)HiSt{&eR6Qi4x- zoDA^0bdJ zKlAO)=ps;5j01e4_a?^L1chXxg2J*pZ7k!5DiLXf>Hhk2*0p+NT$f6 z5|Auv%mm%%FJ)+2WNv(ty`~=9^(uV-emDF2f2LBMy`yA^eY+as>heke-~obt_r6RR z05^jX`xAXz(mf-IAE+AYKFDL6gqdFs`>!9Q{S$FKKfmVmW&rqc{q-TU1^WLY!AEB4 zI&p-fQEv^WRZYrD*pMPvInhMVBH>Uid45cyWX#Hoe-rEQuecv8b$UJ$%^V1RC=l7<3wfFvBmAfGTx&K4Rk;T;%5-k0ozd-`G z}6e zOl4TP*tz7o`{nXuIRzjbE{zP2FlcJ%>6w^_r_KB@b<1ziha^lQWid189@aku%uFZI ztECnFW-tDQ+k^5d_h?vv{>lef=}uOz>eECOxv6&^SASN4vTe#ougC2Tp~bA;9+K(O zEdH8y$sR%Y1Um5cO=5h?6aC{KPsIuI33-gZz)Hj;QjqI$X`ao|)4v2PO=@v@`LZ#~ zn-*DEMF|F{lh?oI_rebN05p*<5Ra=~xClrRXkv2i2#9?HvRL5RdgX!N{TjNeR*%Ux z#rBkeo{e@o%t&kMsn#ZJ|5w`U-J5d7dF8QV zPo^AF!7Z$YO8Qp-Fq*Jxt-BQqjmVRl2A;g5q|KA-|M~pS&n4X6fyZSm)6DA1V}?3p z4KP73e_u#NQ=No4Q!HZ}5`jj2H!5uG(1kC=GC`Y7w*A}y_P`_0TC1o^nTQxD)rnS@ zQv4itVCw@58OqY$=Sk?%bp;g(S_!PuLRpf~dGy7#XqXasJeDz$8BfC-j+~ho z{_YSG`YbdV%>Xt9I$?4^4Th+=uazJGc2XASt{-3%3%KGyKNJ9_qPnUk3s^r1hce@* zH*-`&kzz=iy~%TjH6I9Uk%fPhr_#pW9=>6|vBVXXI019B|_-fenCSaMNI;WC$nk%i_(@!hp*TM4O zP=toGqi*N?Mo3LxLLbF5cORH*VSXvwP7C2-^YuZKKOjLJG+v{jH2vCli43oZqR-Fy ze(@SkBeYgTUo{k7H6$mZAa5D@CCIH7hALi6OXGc%{d#&WYIHNAe=}-yEo^ks|3?WP zN<87cWWt4+Pso(j`p7AaS1KpgeC>PAl@G5@zn-q*qN(k=f(6z! z2|T@N%iS1Pj*-UDs5-wKV}e!h-PUy*Y?GKqZp07r^TszcNicq!@o|2sz+_wMUH1ZM zVXF8v&@Au8CrM6l;9-3q!r4r417b<~tT}(+;M(ELYPb(1<d(+DpZxWGSdE$*bcF zs>Q)&Q9*)wz$Ln`t&7EQ9MyDMGbgS$J zLzEUr#)_Gx=aJGCjx;1`CDvUvi9_p$-Rr-*xBtA=h6s-(sJJ&{@unebo|m-KpNRrm z-_5W6d?ujyieO?qpU0v<*)N321qSHZq4bU zef_B4s6K;%DhLbS%zSlLbu+W1zP?Cx6;N?hHdatwf^WKi7&PV$WqJ8LD-3?0q}_i) z2zDtc8|9|_k>00E<);Y-1t~t?SR|$}u&uEDaM{LEVeI zFF{Kyij#DpZHM|ajvOaL?EOs};n-NB_mS%dwf$`+iY$2yoXNtrjrzsToIzI^nGz&V zS?mJ=UwkZNnfq*kYLJ`MH<6PmB0p~;3$_88!f7JF2-2hR;0A6dT`bb zs1Sv~3)Q~lxC>yR+^^f8i6maU!c~1n2*G!A{|)LACEBiU?6q}tFdkAz0`J*Z^%y%L zS_I|Nu4XE~^HoKdbY*z|vRntL1I?2XPr-+!6%NuJq*&RJYtH8^iGI%}0)2wUkKfoy z><%Q;X_S0AjjTl@`@7mzO@S+8r3;g*JN!@!aH>hpu1x_C3R4QX+HX`9IZTQ}}vpnsK4(rsDybE{~&d(XujFv5g1oQaisc^nY@l!hIe=pq7uV}Y1EnMZ^igurlp3PLZ+;?U1|!O#U! z6-_|I%}*6nd!h4$UPl3B%RwxkJ*^ibJM708Ez8oRBJ9}GzUix4n~QQayd71bQaILS zYDNnu*?%p2CNN^ufSen2hz?IeUNzm2!uOm~5Py5BTuR0Yi8($O6j?;&2HwyBu2AoZvaw!1n~8}oSl3?s8V$3ms-%&@I}kYks=o%Ew?GBSo&q1M zdlxGF~Td0 z-b7-sfBuB#xodfMuTMKqs?p(Yh?S6N~LtKl+Q(4yUnlTsBs4Xhu13H>yFFh5u z>}4y_OW@8B;Awt-BL z-A?^G21iv-hZP;ZH=(;VA~KAXI3_8845tTkOM*C`j+^~ zK`pe|3*QsWOut9w=FWx(EK-rYRh?jhIxhb%h*09T`NsDnV01VCD3u`9bzdx~|M%KZ z)m2SWSEAnkn>0Y>9d$ob5hX&A3zS=+2~EoCLMM1;^d3hpj$fJA+a|Lz{r>Dn$?Ys(I6Ys-gGxE+tfarpzIG2Fr@oqdoANB zALG|N8|jU!6vfO@O)Anej(YjhvS*IqV0Apna(}@}VobA0L#kXj0<{`%?8&3dVOY8F zxga0I7THc2^?folhNZ`Q?xaOYhLoi@_tvex^^}}lhMmF-8uP8RG&NnXYwOKH=`FlcPydq(ItcB4wxRCz zb>oUB*4pc>T;nccG>hZc;@i<*ff8;!v6keNAFv4m_4{}65hwE>{fcC9uxiaK1^r1i5 zn@gx)JjV^~T>Pc>A8Rx8j?&fV>YU{|_WNxzxMWpS_ywFUYA4fTU}E>KQZn}ypkuxN zlnc#z6i$6%#uPObq@YNo!B>a#`R2QxvZk@?m%GABOR*wQ8SH`-i+C;FX<}G?|E83W zd*wJy7^*mWW+Na-f?v-2_<#BEBKjO{nfPO4ch5cBD&8dkzB>58j^G#wd@=v_(t`IU zBwU0V{VxZtLE+WGdrB1N|at^ujUZ6n*68`9x67 z1?dm)qh~-!Q9y20#jVyS*fIkJ)eNp@NVNdH-%0d6yS}ONih;UK9F`}EY2-vFs?EqJ z{h@3$#sR&QXrmO@po3c0{s4-P3XGv+wxNnek#oCp0Rf!;Z3iq5?D;4FI~o~DYGQ7C z$wSUL$H@pYgPb6`NeD>dLaTeSge_TVDf?mP$NI6#eFzqgNsdwTWhH>$RtUR@IgK6MhP z^mfx^?~A8_7ZWFmpF=`|X+aTs1gIpXX2DllI=ws`W1-C}EoIoD5HLdgHIj*&q91Nv zbw1q;%3#;c{6n*{wg{(UHfCnG3Uzdd!J7>>7M;8 zB=`CGsmI@ck<`>=2W4poE2E1R>m!b!+7bc*$w!{TK8udv&V%!-GtXrgYEj0fYs93LuDrZP^EH1P%RMgshe4JdzTSzC}H=1U#) zWFpqk8DWeFD%ftXO;07*SdpD^0 z=MKEGey#FUfl-q+?epTrTg?dZ05=}rKSH*(hRaNV8k2aq!rWXvm+KWLMDZh#I5)gph^yqdWggM@VbFC_r~hV+iT_QJNTu0-ou*RI>o zTs6^fY|3eiW5D8Ww0{ht8?o!Q$>8XXRB8WtPMX{Ket3KN=mn3}EY;5Un$-I#P?qPv z{?w@JnZ?z%e~?>j2|U|e-`CLB55}ep!aknNX|A*SGvLa~&ea1Z5e%?nDd+IFr&Pdngexx7!C^1K@gYqbE~2js*Gn(*Y9P(_Kx! zm2%ZK+suCk2EtW~;nmT9dl!EwC01ETh`y6poh;d zgHrfV#d4fl{INqF-SntXFC#!%WL9|{YSQd&4hk_Lkhoq%P`T=;B!Pv3bl}q>urKq$dl`8jJ`g?jc0z*abLhgf zDaechEA47$3u$DA_i7Epf){a%YuL0a>!-mENf(OyW?6g!IRH*)?17&%)j=VLhLcf zmPjM|?IU_{?*G*`aX|_s*X55`LqWyS*uP81SGW6PRMjX5ikf5VQ+kHg|c zi>65K6p^PQr&DaDBwA32r^U2+Q#W~NZSC4E7gxAb8ZNz zmR!88LS#?FASV}FI&8ql5M*rz?(<7 zI#>~em6b_(=^lj+5KhKyOu%&Dv5eD!`Cnt6A zYB_eF+u8P|C0;*swfyd>tv=q2AGbT!#8m+o`c-KoU1@_b7tf*=$ zX1WJug1_E+;sBpPz{xNOZbJA0wofJ0Kzn_G-LWJkGX~*{n^&u9zk!Tup# z2)M~rPS=JL)6?*(?2k3>zZ+L_^5v8bJeJ0CN<$E>VE;X?LQeeo1g8ijq* z`RYsH1x0;Z{1QkV=-W^a%koQG;5Wv`Vx+u$6I)%_Z}sK1tAc_;*H-O=Lr|gKxqd_s z)Uoxu)1Dh7KMh^o(9%-HhIuC}8d2nxepTq3$w=D3E0o-zYAQ)QKroW+jo1L0nUy|O zC}3Do3009gw;i)n6n-*sH8RTk9Ww;=_;%-po@zAnQsqzD(Pyx!YYo++L8+gVXiF7O z7JMxIKb4(%IF#?d$H!74j3vs_2qD=OvJ0UkDV3}te6ugfzAs~GEZ-z$OR{}2C|g+u zgDiz2`!3s9W-OV(%sBVc@BGd`=Q`(H=f1AFm}kag=6>$={rS9K&qh&BZt*SMqFcag z0)(H&#to~KXk8&WS1&K1mq+xEkl#DxaZ;vHX@GU{73h$l454bcIO;|J|46F;*-30* zdU2zvt#g=JPZ;D^$9$6$w;xdM$_$nWKex^mq`kpZer(?d5q`5o$7-w(1PY+luiK}E z3jMu|(zhS?v>aMxY!zPjgkSwgjr=N@IOkrhYj0&@*3_WlJ@Z=5Ur-ILa(S4awbbIy zal3)_pp*=C_IojFbHpoH+*#mN`?Jp;F3{t69{Pth%gg^12+qB9^Oa1I_q7- z-W^g%9lpRJDDK1*tRY4lYP^P}Nj-5fL_3pccBM~!H1%1DCClp`Sh+ZeDT=I5_O-7T zYgu*@mJU0)dF|yT#iM4Oi&<5<%Qr=83NITh;%+x+yF`p7?^CGV2zd0T}s(uCj`lnC%>dBY~R`Cucbe3p?Kq2DC&`H z_)zmlxz!%O6m=ZaV^lr;Yn{{i=*N6Wken1(eAiIGQ}G-}>qc>OXI3oMLPJmXOt@e( z+*@q=c>ZW;Eu z?3|#eU0~eO`WxT2@Q;4yTOZlE?YdNTocv?fCYbU-UiEKoXbp3jI%Ri&9uIp-&m6j|^qjZ4PbP9iE4e zr_t8P@lcAUA|k&DEMO0J2;XNI^3kqh2gVrAI|1vgf>5)`ejYx9oVT*UW@Mba+XI7X z1uq%I=?a;iH78C)2(cd+HM7{opUTycr|qSVpU%Vto@vsvWHvOXUZ05)ukj+S=h|1T zS5QpuHgb*SOC#^NP&Ry3;yXXv4ZYmZGRJ&8_tNEDdRNM*lRQph?`;+O%G0cyB{Ah+ z4AM5OBj5rn$YiRQc{N;g@=kBiDuEp!~PZC%^=vf}OSi11*Ifllep;@Q9uL5f= z^W*bl1Hvc1Dz^^Jw~8Y9{TZcL=ZJ8)#RD>(X5%xtb`E99C;KT9zuB%bo0JOZhpgTj zdg+SnAB_9_W5d%_K->4bC_SylP`Z1RDLS$ArvGOSuy#m8<5jWWczYw~3Q^s;R zE?2FWXRr6ld|>O}Dklot z)iPcjXEELY>Ln1IX3GnZTJD~Vs%ehcT40Xnu8x0ay~C) z{@K+)OExZBQzKC{NI5XMnM0_G9`^Q!n2^U0sS9Hi1&u~ccrMlc)LuCynXvw1iFmnn z?MeF^KaVysI16J)2({m<_~Ks*^bm`0==!n9Q2ud(#xLhfKMr$QFe5SlVCRU+p_tR5 zxqQfB4Bs>3-EJM7O3H+oP$+zu=khRjo-5u@wrEMXgm~Wcv>S7#Gd(Q*7tM+SPJW%V zX+t7Zglu&zaNe#Z9#-1Bz7|fM4nwedP)kJvqiS}>G0wK6YxJ9~Dv9&i5^oiX zt_r-0(czocoVzUOvkg1Hd=pNoyXnvpzUud`)>Ujcj4U?v65j@%)_(FaJ@2~;<{S}K zjNVpKh4#@#^((vOt;hs;T6fTT!iA2%}B>17((m^ZD#E?{-6YK2AiyrfnheX?!YE8SD z&q^j$_~p2nC|~ZE$k2!;JDE4Q1Tb7YG}H6la^$xpa&H%s5s8Q^ajDDdsPVtuzJbXy zfYUv?Gc935f=W}-n*{wpiF1L2YR-mK0}d$t0(GfmbrnFaPAmEa+QswVu;RB&>d7Y( z)s5s|(%8kP&4q)y{j5->%No;3>!%|^8>w*odp*M%(|d1)U(TGCd)xV;j^7=0XQuk}MvwR9z0`dVWg*S7OzJrCTWcn;&juu2k&WjYx9=L=tAhAi? z+aD@jYyhorw`(QltZH3M4P6V#>xE|@`Pyie58tG_UopjhL>PQjoFovZqSw%$c*e#; zDnqdTO5(?^oK z_27T8s2l`Opo=>L^s{u3z%OI~yaO4OtK}lhBaq9%Uv;1AAZO)Z$wE=x|R8 z=t>Na<#_;E$kEbw(H8~-4zn!4U(76zd;t3p_**Y9Ft(Bh#wL&(2QXJ4f`=VX_EQJ7 z^Uh3++t72m?IfW{6|eK`zQE((I4L#yCr?)Wf8~kE25dilOx&dVI2Jg$v-C}!K{TJ| zD0uzqbAAn8Y%D8-x%C>rp=q;Sb)Bqk{wdKO%QNU?e^C9f>)Yzdayf=$)A>!N#w-EGs@n~@e|J=|aT zK);y;^uzJ88Z;62k_BZR_5OWYev;vh8(`xu04TEmVvQB`IM>*9rLg;N0eIKiflagn ztR>Di{}D(!o6|pHs_SLUnpG_n69a8`s$cy_}xXu}NyB54GGMc9q$)pRaWhFBg{F?CW^SLX# z6Y;%h*BAVj^0x=4HQGp6|M}I24|z1mD^rzwxer2<>t+Hd^+%WB2o)H86v*0ZyVL#~ zuf*1`L1EgY?96zLWE1B*mTiDrcrZL&+_0i~TM=i51CM z`|sUT+pYVu)e@3Cn~KbB&kk#37%cimW8U^<20>!T37kJR>4(?x-}-bko1S@ehW8po z%~QDt3TbdEtVL-iR75gH@{mLaQL+AVc0EyZ7T@>}?gjvb*#Vm|kQjm4M+da248LL8 z;O$k0jc1pFFZNkH6?~IrWLAR{vlZ8sOn(d5w^D;ev|W9;sOq7oP;G7z@_~iOI(VOl zytW$FhKEOITUc1Ip!A}k?K@zjfi1WW7;O@ID>|u^L&AoiL|amF;j@#BQGLA;mLgn= zxha++9dCEMqrbV`Ffh=y{cd|D``apT;Sa_y=1O5J)<$A$>V}1dIW=+xjQUn8Fd&^$ z^kV5ho?8yfa~~vL%{Jhh^#CLjc226C0fKRywycIFzRE($91(fI&i_PWK|q&#_k5zs zy?el6tqF%-t7tx;74I&|bFuN>dMTSJS--VZTIt_C1`#4s0Pn5vR?aj|6$-Ve1b`@l zb%p*Yf9Kk7{@>k4TYW)ClsYi#nS)>~W3NUa95lB!NPI?5saDNr9x=l4p80x79W6A# z%*x<_U7&0Xh6$GbWNNyFY>$4dcC_yD=MuLs^e^OLD4h3Q_y@c_#?y^5m1a?O)>uNG zu-9yhM&njzshR(#UB#9yu1{5L2LB# z@&eElKr!nw=-0a8&3hkBWkeZ$vvvGEo@?^(AuH^5i79lw&oa;{UhZ2(n->6I5n?bZx=qL3 zD@|5U7%+EqG$t?PhGFMLoIwAaOaj_56r!KA)!zrcVgfYi?8s=uRPQ1()SmEakO*f% z-hiO6I_LH4;%#@|y_?e3o~tOrl=gS$ta&T8`tydpezrLoi^+hO5x#BeA6 z6kKzc9-LEAZJ(3~(0FM8KvwstbsspoL7a36?rYq+B*mwrs_X`$&!F9CC*L(Y{Kv!v zauHTO0bFL%pdV?H zm{@u;n2ze|T8Rgo=9n5V$f8E56ve)Wp4P?X_3uykP5VkfFY3ElNe%p+l^d|hXz4u` zP#?U?Vfx_S{NITcS{2q~8H#SAAUq?|*YF5|uE%MU8*yI-e8Jf4`Dx0)&=6iDA|lf2 zDo+RFXH^9gGwAs?b7YYnY;#TswR=DrYB*Ke`NOji7J9s?z;KwWIcj|Ve3y9__ZsDt6U0%0+42A|$(Rm~DXU8pQs;q{pe)E&wVAZjbN9fU zh;9|Q*m+>6Q*^gZ25UViq65>S&gPMbh92{cF=B{mFmQq4-^Z~ZE8yP5sh5A< zOhx=8cNQEZn_SQCYY%2O zRq^`v`E(4doA94AVMkJ~le-d|pLaro37by!%l%>4RviWc&}flXNT{Q80A_`LD3y@! z_$cc+=xnyX;!OMxvqyB4upl8P;zmg)=TR^#;Br)b_*s@<_7hB4IcJ_LRtEIy!CLYnbw=yL;O?JR;Sg^E z3W9+EH={A?LTWev4Ee7Hdjlh5&4SD|Grr%YqlF;2xg4GenCP?8D|MX&7W>^JJ*?&ktQg~^Aq?7vs1+B z6KDMzU7(Fghdfxg4W*d^v6O*( zlsRIU_RJ&|utgpKsXRgaMG*+9%8^C}le2f7gPxt_5+$Q2?B9GP8}g ztu^X!!m^$}^pa1x|qL#L;3bNpU;_Kyhj6S>1^J`2mOtyMX(0X7c}gj4@M=%O+e4*S;*@*R5lCN1qP#q&UZv;D(# zW$(BEk|N&Cw^dp_-wm8Hp_wxy?u!NA(hk<94Gz^*LnwJp3PTz~T=5_q4TuljiKkz5 zaF{FLjG-^N$rROkqC^+M6oFzQwzP8y5(5}=M@|oiLS7)KO!Zzb2PP<}LN^zVXt2-u W94-}Z`-6LOz^-4rrHj^n6!{-taK{P& literal 0 HcmV?d00001 diff --git a/hw2/hw/assets/error_rate_4xx.png b/hw2/hw/assets/error_rate_4xx.png new file mode 100644 index 0000000000000000000000000000000000000000..e32731b2bfe715c054d1795e3c53d8027112fa0c GIT binary patch literal 21543 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 literal 0 HcmV?d00001 diff --git a/hw2/hw/assets/https_status_codes.png b/hw2/hw/assets/https_status_codes.png new file mode 100644 index 0000000000000000000000000000000000000000..5469ddbaac8de57a658f43ff079d4c7febe262fb GIT binary patch literal 30268 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