From bd3438331d9b17cd07b93329457ae4c3e0b8ee0b Mon Sep 17 00:00:00 2001 From: xile Date: Tue, 4 Nov 2025 21:51:48 -0800 Subject: [PATCH 1/4] wave1-3 --- .gitignore | 1 + app/__init__.py | 2 + app/models/goal.py | 3 + app/models/task.py | 36 ++++++++ app/routes/routes_utilities.py | 43 ++++++++++ app/routes/task_routes.py | 121 ++++++++++++++++++++++++++- migrations/README | 1 + migrations/alembic.ini | 50 +++++++++++ migrations/env.py | 113 +++++++++++++++++++++++++ migrations/script.py.mako | 24 ++++++ migrations/versions/af48238dc931_.py | 36 ++++++++ tests/test_wave_01.py | 46 +++++----- tests/test_wave_02.py | 4 +- tests/test_wave_03.py | 20 ++--- 14 files changed, 466 insertions(+), 34 deletions(-) create mode 100644 app/routes/routes_utilities.py create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/af48238dc931_.py diff --git a/.gitignore b/.gitignore index 4e9b18359..db601f26e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode .DS_Store +.env # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/app/__init__.py b/app/__init__.py index 3c581ceeb..190dc8743 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,6 +1,7 @@ from flask import Flask from .db import db, migrate from .models import task, goal +from app.routes.task_routes import bp as tasks_bp import os def create_app(config=None): @@ -18,5 +19,6 @@ def create_app(config=None): migrate.init_app(app, db) # Register Blueprints here + app.register_blueprint(tasks_bp) return app diff --git a/app/models/goal.py b/app/models/goal.py index 44282656b..a8bb5bf92 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -1,5 +1,8 @@ from sqlalchemy.orm import Mapped, mapped_column from ..db import db +#from sqlalchemy import Date + class Goal(db.Model): id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index 5d99666a4..ead2197df 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,5 +1,41 @@ +from flask_sqlalchemy import SQLAlchemy from sqlalchemy.orm import Mapped, mapped_column from ..db import db +from sqlalchemy import String, Date +from datetime import datetime +from flask import Blueprint, abort, make_response, request, Response,jsonify class Task(db.Model): id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + title: Mapped[str] = mapped_column(String(100), nullable=False) + description: Mapped[str] = mapped_column(String(255)) + completed_at:Mapped[datetime.date] = mapped_column(Date, nullable=True) + + + def to_dict(self): # json + + + return {"id": self.id, + "title":self.title, + "description":self.description, + #"is_complete":self.completed_at if self.completed_at else False + "is_complete": self.completed_at is not None} + + + + @classmethod + def from_dict(cls, data_dict): + if "title" not in data_dict: + #abort(make_response({"details": "Invalid data"}, 400)) + raise KeyError("title") + if "description" not in data_dict: + #abort(make_response({"details": "Invalid data"}, 400)) + raise KeyError("description") + return cls( + title=data_dict["title"], + description=data_dict["description"], + completed_at=None # default to None when creating + ) + + + \ No newline at end of file diff --git a/app/routes/routes_utilities.py b/app/routes/routes_utilities.py new file mode 100644 index 000000000..d6e1938a8 --- /dev/null +++ b/app/routes/routes_utilities.py @@ -0,0 +1,43 @@ +from flask import abort, make_response +from ..db import db + +def validate_model(cls, id): + try: + id = int(id) + except ValueError: + invalid = {"message": f"{cls.__name__} id({id}) is invalid."} + abort(make_response(invalid, 400)) + + query = db.select(cls).where(cls.id == id) + model = db.session.scalar(query) + + if not model: + not_found = {"message": f"{cls.__name__} with id({id}) not found."} + abort(make_response(not_found, 404)) + + return model + + +def create_model(cls, model_data): + try: + new_model = cls.from_dict(model_data) + except KeyError as e: + response = {"message": f"Invalid request: missing {e.args[0]}"} + abort(make_response(response, 400)) + + db.session.add(new_model) + db.session.commit() + + return new_model.to_dict(), 201 + +def get_models_with_filters(cls, filters=None): + query = db.select(cls) + + if filters: + for attribute, value in filters.items(): + if hasattr(cls, attribute): + query = query.where(getattr(cls, attribute).ilike(f"%{value}%")) + + models = db.session.scalars(query.order_by(cls.id)) + models_response = [model.to_dict() for model in models] + return models_response diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py index 3aae38d49..ffe83a780 100644 --- a/app/routes/task_routes.py +++ b/app/routes/task_routes.py @@ -1 +1,120 @@ -from flask import Blueprint \ No newline at end of file +from ..models.task import Task +from flask import Blueprint, abort, make_response, request, Response,jsonify +from ..db import db +from .routes_utilities import validate_model,create_model,get_models_with_filters +from datetime import datetime + +bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") + +@bp.post("") +def create_task(): + + request_body = request.get_json() + + try: + new_task = Task.from_dict(request_body) + except KeyError: + return jsonify({"details": "Invalid data"}), 400 + + db.session.add(new_task) + db.session.commit() + return new_task.to_dict(), 201 + + +@bp.get("") + +def get_all_tasks(): + query = db.select(Task) + + id_param = request.args.get("id") + if id_param: + query = query.where(Task.id == int(id_param)) + + description_param = request.args.get("description") + if description_param: + query = query.where(Task.description.ilike(f"%{description_param}%")) + + title_param = request.args.get("title") + sort_param = request.args.get("sort") + if title_param: + query = query.where(Task.title.ilike(f"%{title_param}%")) + + complete_param = request.args.get("is_complete") + if complete_param == "true": + + query = query.where(Task.completed_at.is_not(None)) + + elif complete_param == "false": + + query = query.where(Task.completed_at.is_(None)) + + #query = query.order_by(Task.id) + if sort_param =="asc": + query = query = query.order_by(Task.title.asc()) + elif sort_param == "desc": + query = query.order_by(Task.title.desc()) + else: + query = query.order_by(Task.id) + tasks = db.session.scalars(query) + + result_list = [] + + result_list =[task.to_dict()for task in tasks] + + return jsonify(result_list or []),200 + + +@bp.get("/") + +def get_one_task(id): + + task =validate_model(Task,id) + return task.to_dict() + + +@bp.put("/") +def replace_task(id): + task = validate_model(Task, id) + + request_body = request.get_json() + + # if request_body["is_complete"]: + # task.completed_at = datetime.utcnow() + # else: + # task.completed_at = None + task.title = request_body["title"] + + task.description = request_body["description"] + + db.session.commit() + + return Response(status = 204,mimetype ="application/json") + +@bp.delete("/") +def del_task(id): + task = validate_model(Task, id) + + db.session.delete(task) + + db.session.commit() + + return Response(status = 204,mimetype ="application/json") + + + + +@bp.patch("//mark_complete") +def mark_complete(id): + task = validate_model(Task, id) + task.completed_at = datetime.utcnow() + db.session.commit() + return Response(status=204, mimetype="application/json") + + +@bp.patch("//mark_incomplete") +def mark_incomplete(id): + task = validate_model(Task, id) + + task.completed_at = None + db.session.commit() + return Response(status=204, mimetype="application/json") diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..0e0484415 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 000000000..ec9d45c26 --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..4c9709271 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/af48238dc931_.py b/migrations/versions/af48238dc931_.py new file mode 100644 index 000000000..c4a9d2e70 --- /dev/null +++ b/migrations/versions/af48238dc931_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: af48238dc931 +Revises: +Create Date: 2025-10-31 14:54:10.327215 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'af48238dc931' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('goal', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('task') + op.drop_table('goal') + # ### end Alembic commands ### diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index fac95a0a3..d0affd4f3 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -2,7 +2,7 @@ from app.db import db import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_task_to_dict(): #Arrange new_task = Task(id = 1, title="Make My Bed", @@ -19,7 +19,7 @@ def test_task_to_dict(): assert task_dict["description"] == "Start the day off right!" assert task_dict["is_complete"] == False -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_task_to_dict_missing_id(): #Arrange new_task = Task(title="Make My Bed", @@ -36,7 +36,7 @@ def test_task_to_dict_missing_id(): assert task_dict["description"] == "Start the day off right!" assert task_dict["is_complete"] == False -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_task_to_dict_missing_title(): #Arrange new_task = Task(id = 1, @@ -53,7 +53,7 @@ def test_task_to_dict_missing_title(): assert task_dict["description"] == "Start the day off right!" assert task_dict["is_complete"] == False -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_task_from_dict(): #Arrange task_dict = { @@ -70,7 +70,7 @@ def test_task_from_dict(): assert task_obj.description == "Start the day off right!" assert task_obj.completed_at is None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_task_from_dict_no_title(): #Arrange task_dict = { @@ -82,7 +82,7 @@ def test_task_from_dict_no_title(): with pytest.raises(KeyError, match = 'title'): Task.from_dict(task_dict) -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_task_from_dict_no_description(): #Arrange task_dict = { @@ -94,7 +94,7 @@ def test_task_from_dict_no_description(): with pytest.raises(KeyError, match = 'description'): Task.from_dict(task_dict) -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_no_saved_tasks(client): # Act response = client.get("/tasks") @@ -105,7 +105,7 @@ def test_get_tasks_no_saved_tasks(client): assert response_body == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_one_saved_tasks(client, one_task): # Act response = client.get("/tasks") @@ -124,7 +124,7 @@ def test_get_tasks_one_saved_tasks(client, one_task): ] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_task(client, one_task): # Act response = client.get("/tasks/1") @@ -140,7 +140,7 @@ def test_get_task(client, one_task): } -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_task_not_found(client): # Act response = client.get("/tasks/1") @@ -148,14 +148,17 @@ def test_get_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {"message": "Task with id(1) not found."} - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** + + -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_task(client): # Act response = client.post("/tasks", json={ @@ -181,7 +184,7 @@ def test_create_task(client): assert new_task.description == "Test Description" assert new_task.completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_update_task(client, one_task): # Act response = client.put("/tasks/1", json={ @@ -201,7 +204,7 @@ def test_update_task(client, one_task): -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_update_task_not_found(client): # Act response = client.put("/tasks/1", json={ @@ -212,14 +215,15 @@ def test_update_task_not_found(client): # Assert assert response.status_code == 404 + assert response_body == {"message": "Task with id(1) not found."} - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task(client, one_task): # Act response = client.delete("/tasks/1") @@ -230,7 +234,7 @@ def test_delete_task(client, one_task): query = db.select(Task).where(Task.id == 1) assert db.session.scalar(query) == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task_not_found(client): # Act response = client.delete("/tasks/1") @@ -238,8 +242,8 @@ def test_delete_task_not_found(client): # Assert assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") + assert response_body == {"message": "Task with id(1) not found."} + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** @@ -247,7 +251,7 @@ def test_delete_task_not_found(client): assert db.session.scalars(db.select(Task)).all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_title(client): # Act response = client.post("/tasks", json={ @@ -264,7 +268,7 @@ def test_create_task_must_contain_title(client): assert db.session.scalars(db.select(Task)).all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_description(client): # Act response = client.post("/tasks", json={ diff --git a/tests/test_wave_02.py b/tests/test_wave_02.py index a087e0909..c9a76e6b1 100644 --- a/tests/test_wave_02.py +++ b/tests/test_wave_02.py @@ -1,7 +1,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_sorted_asc(client, three_tasks): # Act response = client.get("/tasks?sort=asc") @@ -29,7 +29,7 @@ def test_get_tasks_sorted_asc(client, three_tasks): ] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_sorted_desc(client, three_tasks): # Act response = client.get("/tasks?sort=desc") diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index d7d441695..d4c78c6a5 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -6,7 +6,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_incomplete_task(client, one_task): # Arrange """ @@ -34,7 +34,7 @@ def test_mark_complete_on_incomplete_task(client, one_task): assert db.session.scalar(query).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_complete_task(client, completed_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -46,7 +46,7 @@ def test_mark_incomplete_on_complete_task(client, completed_task): assert db.session.scalar(query).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_completed_task(client, completed_task): # Arrange """ @@ -74,7 +74,7 @@ def test_mark_complete_on_completed_task(client, completed_task): query = db.select(Task).where(Task.id == 1) assert db.session.scalar(query).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_incomplete_task(client, one_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -86,7 +86,7 @@ def test_mark_incomplete_on_incomplete_task(client, one_task): assert db.session.scalar(query).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_missing_task(client): # Act response = client.patch("/tasks/1/mark_complete") @@ -94,14 +94,14 @@ def test_mark_complete_missing_task(client): # Assert assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") + assert response_body == {"message": "Task with id(1) not found."} + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_missing_task(client): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -109,8 +109,8 @@ def test_mark_incomplete_missing_task(client): # Assert assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") + assert response_body == {"message": "Task with id(1) not found."} + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** From 61c31683897b1b111a43dde20dd801808129a218 Mon Sep 17 00:00:00 2001 From: xile Date: Thu, 6 Nov 2025 15:50:24 -0800 Subject: [PATCH 2/4] wave5-7 --- app/__init__.py | 3 + app/models/goal.py | 21 +++- app/models/task.py | 16 ++- app/routes/goal_routes.py | 101 +++++++++++++++++- ...routes_utilities.py => route_utilities.py} | 5 +- app/routes/task_routes.py | 5 +- migrations/versions/52388701adf4_.py | 34 ++++++ migrations/versions/b73fa682b647_.py | 42 ++++++++ tests/test_wave_05.py | 67 ++++++++---- tests/test_wave_06.py | 16 +-- tests/test_wave_07.py | 46 +++++--- 11 files changed, 301 insertions(+), 55 deletions(-) rename app/routes/{routes_utilities.py => route_utilities.py} (86%) create mode 100644 migrations/versions/52388701adf4_.py create mode 100644 migrations/versions/b73fa682b647_.py diff --git a/app/__init__.py b/app/__init__.py index 190dc8743..ae00aed39 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,6 +2,7 @@ from .db import db, migrate from .models import task, goal from app.routes.task_routes import bp as tasks_bp +from app.routes.goal_routes import bp as goals_bp import os def create_app(config=None): @@ -20,5 +21,7 @@ def create_app(config=None): # Register Blueprints here app.register_blueprint(tasks_bp) + + app.register_blueprint(goals_bp) return app diff --git a/app/models/goal.py b/app/models/goal.py index a8bb5bf92..2cfdb7f74 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -1,8 +1,25 @@ -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.orm import Mapped, mapped_column,relationship from ..db import db +from sqlalchemy import String #from sqlalchemy import Date class Goal(db.Model): + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - \ No newline at end of file + title: Mapped[str] = mapped_column(String(100), nullable=False) + tasks: Mapped[list["Task"]] = relationship( + back_populates="goal") + + + def to_dict(self): + return { + "id": self.id, + "title": self.title + } + + @classmethod + def from_dict(cls, data_dict): + if "title" not in data_dict: + raise KeyError("title") + return cls(title=data_dict["title"]) \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index ead2197df..ee8a485e2 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,9 +1,11 @@ from flask_sqlalchemy import SQLAlchemy -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.orm import Mapped, mapped_column,relationship +from sqlalchemy import ForeignKey from ..db import db from sqlalchemy import String, Date from datetime import datetime from flask import Blueprint, abort, make_response, request, Response,jsonify +from typing import Optional class Task(db.Model): id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) @@ -11,22 +13,30 @@ class Task(db.Model): description: Mapped[str] = mapped_column(String(255)) completed_at:Mapped[datetime.date] = mapped_column(Date, nullable=True) + goal_id: Mapped[Optional[int]] = mapped_column(ForeignKey("goal.id")) + goal: Mapped[Optional["Goal"]] = relationship(back_populates="tasks") def to_dict(self): # json - return {"id": self.id, + task_dict = {"id": self.id, "title":self.title, "description":self.description, #"is_complete":self.completed_at if self.completed_at else False "is_complete": self.completed_at is not None} + if self.goal_id is not None: + task_dict["goal_id"] = self.goal_id + + + return task_dict + @classmethod def from_dict(cls, data_dict): if "title" not in data_dict: - #abort(make_response({"details": "Invalid data"}, 400)) + raise KeyError("title") if "description" not in data_dict: #abort(make_response({"details": "Invalid data"}, 400)) diff --git a/app/routes/goal_routes.py b/app/routes/goal_routes.py index 3aae38d49..451c562c6 100644 --- a/app/routes/goal_routes.py +++ b/app/routes/goal_routes.py @@ -1 +1,100 @@ -from flask import Blueprint \ No newline at end of file +from flask import Blueprint, jsonify, request, abort, make_response +from ..db import db +from ..models.goal import Goal +from ..models.task import Task +from .route_utilities import validate_model + + +bp = Blueprint("goals_bp", __name__, url_prefix="/goals") + + + +@bp.post("") +def create_goal(): + request_body = request.get_json() + try: + new_goal = Goal.from_dict(request_body) + except KeyError: + return jsonify({"details": "Invalid data"}), 400 + + db.session.add(new_goal) + db.session.commit() + + return jsonify(new_goal.to_dict()), 201 + + +@bp.get("") +def get_all_goals(): + goals = Goal.query.order_by(Goal.id).all() + return jsonify([goal.to_dict() for goal in goals]), 200 + + +@bp.get("/") +def get_one_goal(id): + goal = validate_model(Goal, id) + return jsonify(goal.to_dict()), 200 + + + + +@bp.put("/") +def update_goal(id): + goal = validate_model(Goal, id) + request_body = request.get_json() + + goal.title = request_body["title"] + + db.session.commit() + return jsonify(goal.to_dict()), 200 + + + +@bp.delete("/") +def delete_goal(id): + goal = validate_model(Goal, id) + + db.session.delete(goal) + db.session.commit() + + return jsonify({"message": f'Goal {goal.id} successfully deleted'}), 204 + + +#nested +@bp.post("//tasks") +def add_tasks_to_goal(goal_id): + goal = validate_model(Goal, goal_id) + request_body = request.get_json() + + task_ids = request_body.get("task_ids", []) + + for task in goal.tasks: + task.goal_id = None + + + + for task_id in task_ids: + task = validate_model(Task, task_id) + task.goal_id = goal.id + + db.session.commit() + + return jsonify({ + "id": goal.id, + "task_ids": task_ids + }), 200 + + + +@bp.get("//tasks") +def get_tasks_for_goal(goal_id): + goal = validate_model(Goal, goal_id) + + tasks_response = [task.to_dict() for task in goal.tasks] + + goal_dict = goal.to_dict() + goal_dict["tasks"] = tasks_response + + return jsonify(goal_dict), 200 + + + diff --git a/app/routes/routes_utilities.py b/app/routes/route_utilities.py similarity index 86% rename from app/routes/routes_utilities.py rename to app/routes/route_utilities.py index d6e1938a8..78f412297 100644 --- a/app/routes/routes_utilities.py +++ b/app/routes/route_utilities.py @@ -22,8 +22,9 @@ def create_model(cls, model_data): try: new_model = cls.from_dict(model_data) except KeyError as e: - response = {"message": f"Invalid request: missing {e.args[0]}"} - abort(make_response(response, 400)) + #response = {"message": f"Invalid request: missing {e.args[0]}"} + #abort(make_response(response, 400)) + abort(make_response({"details": "Invalid data"}, 400)) db.session.add(new_model) db.session.commit() diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py index ffe83a780..16bc5f0a3 100644 --- a/app/routes/task_routes.py +++ b/app/routes/task_routes.py @@ -1,7 +1,7 @@ from ..models.task import Task from flask import Blueprint, abort, make_response, request, Response,jsonify from ..db import db -from .routes_utilities import validate_model,create_model,get_models_with_filters +from .route_utilities import validate_model,create_model,get_models_with_filters from datetime import datetime bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @@ -14,7 +14,8 @@ def create_task(): try: new_task = Task.from_dict(request_body) except KeyError: - return jsonify({"details": "Invalid data"}), 400 + #return jsonify({"details": "Invalid data"}), 400 + abort(make_response({"details": "Invalid data"}, 400)) db.session.add(new_task) db.session.commit() diff --git a/migrations/versions/52388701adf4_.py b/migrations/versions/52388701adf4_.py new file mode 100644 index 000000000..a4de72f7d --- /dev/null +++ b/migrations/versions/52388701adf4_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 52388701adf4 +Revises: b73fa682b647 +Create Date: 2025-11-05 16:55:55.917780 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '52388701adf4' +down_revision = 'b73fa682b647' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('task', schema=None) as batch_op: + batch_op.add_column(sa.Column('goal_id', sa.Integer(), nullable=True)) + batch_op.create_foreign_key(None, 'goal', ['goal_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('task', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_column('goal_id') + + # ### end Alembic commands ### diff --git a/migrations/versions/b73fa682b647_.py b/migrations/versions/b73fa682b647_.py new file mode 100644 index 000000000..5d7e32073 --- /dev/null +++ b/migrations/versions/b73fa682b647_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: b73fa682b647 +Revises: af48238dc931 +Create Date: 2025-11-05 16:50:04.362330 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b73fa682b647' +down_revision = 'af48238dc931' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('goal', schema=None) as batch_op: + batch_op.add_column(sa.Column('title', sa.String(length=100), nullable=False)) + + with op.batch_alter_table('task', schema=None) as batch_op: + batch_op.add_column(sa.Column('title', sa.String(length=100), nullable=False)) + batch_op.add_column(sa.Column('description', sa.String(length=255), nullable=False)) + batch_op.add_column(sa.Column('completed_at', sa.Date(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('task', schema=None) as batch_op: + batch_op.drop_column('completed_at') + batch_op.drop_column('description') + batch_op.drop_column('title') + + with op.batch_alter_table('goal', schema=None) as batch_op: + batch_op.drop_column('title') + + # ### end Alembic commands ### diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index b7cc330ae..2dfbaf873 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,7 +1,7 @@ from app.models.goal import Goal import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_goal_to_dict(): #Arrange new_goal = Goal(id=1, title="Seize the Day!") @@ -13,7 +13,7 @@ def test_goal_to_dict(): assert goal_dict["id"] == 1 assert goal_dict["title"] == "Seize the Day!" -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_goal_to_dict_no_id(): #Arrange new_goal = Goal(title="Seize the Day!") @@ -25,7 +25,7 @@ def test_goal_to_dict_no_id(): assert goal_dict["id"] is None assert goal_dict["title"] == "Seize the Day!" -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_goal_to_dict_no_title(): #Arrange new_goal = Goal(id=1) @@ -39,7 +39,7 @@ def test_goal_to_dict_no_title(): -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_goal_from_dict(): #Arrange goal_dict = { @@ -52,7 +52,7 @@ def test_goal_from_dict(): #Assert assert goal_obj.title == "Seize the Day!" -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_goal_from_dict_no_title(): #Arrange goal_dict = { @@ -63,7 +63,7 @@ def test_goal_from_dict_no_title(): Goal.from_dict(goal_dict) -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_no_saved_goals(client): # Act response = client.get("/goals") @@ -74,7 +74,7 @@ def test_get_goals_no_saved_goals(client): assert response_body == [] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_one_saved_goal(client, one_goal): # Act response = client.get("/goals") @@ -91,7 +91,7 @@ def test_get_goals_one_saved_goal(client, one_goal): ] -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_goal(client, one_goal): # Act response = client.get("/goals/1") @@ -105,14 +105,17 @@ def test_get_goal(client, one_goal): } -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_get_goal_not_found(client): pass # Act response = client.get("/goals/1") response_body = response.get_json() - raise Exception("Complete test") + #raise Exception("Complete test") + + assert response.status_code == 404 + assert response_body == {"message": "Goal with id(1) not found."} # Assert # ---- Complete Test ---- # assertion 1 goes here @@ -120,7 +123,7 @@ def test_get_goal_not_found(client): # ---- Complete Test ---- -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_goal(client): # Act response = client.post("/goals", json={ @@ -136,12 +139,20 @@ def test_create_goal(client): } -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_update_goal(client, one_goal): - raise Exception("Complete test") + #raise Exception("Complete test") # Act # ---- Complete Act Here ---- - + response = client.put("/goals/1", json={ + "title": "My New Goal" + }) + response_body = response.get_json() + assert response.status_code == 200 + assert response_body == { + "id": 1, + "title": "My New Goal" + } # Assert # ---- Complete Assertions Here ---- # assertion 1 goes here @@ -150,20 +161,26 @@ def test_update_goal(client, one_goal): # ---- Complete Assertions Here ---- -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_update_goal_not_found(client): - raise Exception("Complete test") + #raise Exception("Complete test") # Act # ---- Complete Act Here ---- - + response = client.put("/goals/100", json={ + "title": "My New Goal" + }) + response_body = response.get_json() + # Assert # ---- Complete Assertions Here ---- # assertion 1 goes here # assertion 2 goes here + assert response.status_code == 404 + assert response_body == {"message": "Goal with id(100) not found."} # ---- Complete Assertions Here ---- -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_delete_goal(client, one_goal): # Act response = client.delete("/goals/1") @@ -178,27 +195,33 @@ def test_delete_goal(client, one_goal): response_body = response.get_json() assert "message" in response_body - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") + assert response_body["message"] == "Goal with id(1) not found." # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="test to be completed by student") +#@pytest.mark.skip(reason="test to be completed by student") def test_delete_goal_not_found(client): - raise Exception("Complete test") + #raise Exception("Complete test") + response = client.delete("/goals/100") + response_body = response.get_json() + # Act # ---- Complete Act Here ---- # Assert + assert response.status_code == 404 + assert response_body == {"message": "Goal with id(100) not found."} # ---- Complete Assertions Here ---- # assertion 1 goes here # assertion 2 goes here # ---- Complete Assertions Here ---- -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_create_goal_missing_title(client): # Act response = client.post("/goals", json={}) diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 727fce93a..419a5eb90 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -3,7 +3,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal(client, one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -25,7 +25,7 @@ def test_post_task_ids_to_goal(client, one_goal, three_tasks): assert len(db.session.scalar(query).tasks) == 3 -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal_overwrites_existing_tasks(client, one_task_belongs_to_one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -45,7 +45,7 @@ def test_post_task_ids_to_goal_overwrites_existing_tasks(client, one_task_belong assert len(db.session.scalar(query).tasks) == 2 -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_goal(client): # Act response = client.get("/goals/1/tasks") @@ -53,14 +53,16 @@ def test_get_tasks_for_specific_goal_no_goal(client): # Assert assert response.status_code == 404 + + assert response_body == {"message": "Goal with id(1) not found."} - raise Exception("Complete test with assertion about response body") + #raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): # Act response = client.get("/goals/1/tasks") @@ -77,7 +79,7 @@ def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): } -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): # Act response = client.get("/goals/1/tasks") @@ -102,7 +104,7 @@ def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): } -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_get_task_includes_goal_id(client, one_task_belongs_to_one_goal): response = client.get("/tasks/1") response_body = response.get_json() diff --git a/tests/test_wave_07.py b/tests/test_wave_07.py index 7e7cef55a..22776bc8e 100644 --- a/tests/test_wave_07.py +++ b/tests/test_wave_07.py @@ -4,7 +4,7 @@ from app.models.task import Task from app.routes.route_utilities import create_model, validate_model -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_validate_model_with_task(client, three_tasks): #Act task_1 = validate_model(Task, 1) @@ -24,7 +24,7 @@ def test_route_utilities_validate_model_with_task(client, three_tasks): assert task_3.title == "Pay my outstanding tickets 😭" -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_validate_model_with_task_invalid_id(client, three_tasks): #Act & Assert # Calling `validate_model` without being invoked by a route will @@ -35,25 +35,28 @@ def test_route_utilities_validate_model_with_task_invalid_id(client, three_tasks # Test that the correct status code and response message are returned response = e.value.get_response() assert response.status_code == 400 - - raise Exception("Complete test with an assertion about the response body") + assert response.get_json() == {"message": "Task id(One) is invalid."} + #raise Exception("Complete test with an assertion about the response body") # ***************************************************************************** # ** Complete test with an assertion about the response body **************** # ***************************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_validate_model_with_task_missing_id(client, three_tasks): #Act & Assert with pytest.raises(HTTPException) as e: result_task = validate_model(Task, 4) - raise Exception("Complete test with assertion status code and response body") + response = e.value.get_response() + #raise Exception("Complete test with assertion status code and response body") + assert response.status_code == 404 + assert response.get_json() == {"message": "Task with id(4) not found."} # ***************************************************************************** # **Complete test with assertion about status code response body*************** # ***************************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_validate_model_with_goal(client, one_goal): #Act goal_1 = validate_model(Goal, 1) @@ -62,29 +65,36 @@ def test_route_utilities_validate_model_with_goal(client, one_goal): assert goal_1.id == 1 assert goal_1.title == "Build a habit of going outside daily" -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_validate_model_with_goal_invalid_id(client, one_goal): #Act & Assert with pytest.raises(HTTPException) as e: result_task = validate_model(Goal, "One") - raise Exception("Complete test with assertion status code and response body") + + response = e.value.get_response() + assert response.status_code == 400 + assert response.get_json() == {"message": "Goal id(One) is invalid."} + #raise Exception("Complete test with assertion status code and response body") # ***************************************************************************** # **Complete test with assertion about status code response body*************** # ***************************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_validate_model_with_goal_missing_id(client, one_goal): #Act & Assert with pytest.raises(HTTPException) as e: result_task = validate_model(Goal, 4) - raise Exception("Complete test with assertion status code and response body") + response = e.value.get_response() + #raise Exception("Complete test with assertion status code and response body") # ***************************************************************************** # **Complete test with assertion about status code response body*************** + assert response.status_code == 404 + assert response.get_json() == {"message": "Goal with id(4) not found."} # ***************************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_create_model_with_task(client): #Arrange request_body = { @@ -103,7 +113,7 @@ def test_route_utilities_create_model_with_task(client): assert response[0]["is_complete"] == False assert response[1] == 201 -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_create_model_with_task_missing_title(client): #Arrange request_body = { @@ -120,7 +130,7 @@ def test_route_utilities_create_model_with_task_missing_title(client): assert response.get_json() == {"details": "Invalid data"} -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_create_model_with_goal(client): #Arrange request_body = { @@ -135,7 +145,7 @@ def test_route_utilities_create_model_with_goal(client): assert response[0]["title"] == "Seize the Day!" assert response[1] == 201 -@pytest.mark.skip(reason="No way to test this feature yet") +#@pytest.mark.skip(reason="No way to test this feature yet") def test_route_utilities_create_model_with_goal_missing_title(client): #Arrange request_body = { @@ -145,7 +155,11 @@ def test_route_utilities_create_model_with_goal_missing_title(client): with pytest.raises(HTTPException) as e: create_model(Goal, request_body) - raise Exception("Complete test with assertion status code and response body") + + response = e.value.get_response() + assert response.status_code == 400 + assert response.get_json() == {"details": "Invalid data"} + #raise Exception("Complete test with assertion status code and response body") # ***************************************************************************** # **Complete test with assertion about status code response body*************** # ***************************************************************************** From 98afc1c8c9c308560ec6b61d1b2fee4b67bbf5dc Mon Sep 17 00:00:00 2001 From: xile Date: Thu, 6 Nov 2025 21:46:15 -0800 Subject: [PATCH 3/4] wave4 change --- ada-project-docs/assets/lexy-slackbot.png | Bin 0 -> 86459 bytes app/__init__.py | 3 ++ app/routes/task_routes.py | 44 ++++++++++++++++++---- 3 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 ada-project-docs/assets/lexy-slackbot.png diff --git a/ada-project-docs/assets/lexy-slackbot.png b/ada-project-docs/assets/lexy-slackbot.png new file mode 100644 index 0000000000000000000000000000000000000000..54ca54eb147c92a580bbca8db37757a2dc47909b GIT binary patch literal 86459 zcmeFYg;!ij^8gA2g3G|*1Q}d{yUPHBYmngX?(Xgu+}(mifZ*;BAh^2*4=#`F?k8{e z`~3s&ygO&k?b}^dUENjPx1?``qP!$35&;qv6cnly1gs1N121_qmVx8fNKjXNh59GjO+40eygW(ovqhU)YQa9cHNf46i)To_DZrlez@Ho z5!%h-yB>Q3wU_N(&W=9)np97jGzuBjSPmj(lJE^0`};aN6k(e%<91IESQO>;VtXpr zEOdc&4L9wVr}<~Ss8~e`Tqyq@;ViOb#H?r)br71U zc43dESWZyy1DwAa)0SpPAVZm0*aQr2gd#%#HDCne=%92;Mr9t(Q!iFiq&QsCI{p{~Xi?K>|OwZ^urswdK-U%PgCT_88Q2 zGLy<}-jC;k0lBTRUo6Yk06Sq4C&nRpNN5_8gJM26Et28e@WQR8Gr%iO3-S4-G7-Xb zkUV}Ab`NU;1-oK(T#DI{=*H_Kx7}fQqkehgC=aDqH;y8%qG`P9bL7x^+d|QBhuBiw z5kb75+u(stjo^YjeU~F{O$w=K^^3-U&!o)q0yu8*JH04#n*jwaij;QH4uLJqzV_Az z(Ars+J}uz)7)g0oST!T-Vf|%~o!3E-4mc5iIAE=*e>o|9_@Wnq*P4)v0>auW`~euy z8hZyyEJW=WWQ~Iu7$8K334Qy$QTSaA0#Nknf>~`aB8>ajG@>!uCG9)MPNVP(5!ivC zXKXsk!FYJ7HGpx?^JzGGB~)KLd}aOf>P66zdg!b40cZ&pZt^HWb14+6*MRPDGO*VY z!gaULDa0Mse;V6tQ#Z)L`wKNcl`{r0#zz1zVna!Ilu36B5jtYS1`4UGvbrN1GUchQHS z5BKqrth1{-l0U8b6x*7_1iE*3eUp61tjR;Q3}tG?<}{BeT#2nZ380brVmSj(QVYmgkj3??(aBjX2UI#lf zm1!)Gfpv70xKX(V6;=0+VX+2}TFkW3=&7JyCvP3`k$`*;J~y&kAU!vVLJ!;xyEkJS zN==W|>FWn}U%2+T>kT~w_CS`-gf+P}02M|(!W1H9uw$2emq8c)4TB3{1tS7XHSu8& zF}D|Jh~|LffbW3iKyn6K7jzRpCkaYGk&30zQ)Z7)pd(F*=ZwFKk4<7s`UW`}ATVaD zr{@XIlKv1MHMqUCyk)dCyhXhg2egUg=*`5G11Os+t0}rFX3*l&>LkahzNvUSothy$ zn$MW0rg&IxR_0b#!_Lmmz}~`c$k9V^!9mA(RGD5<{$2ZHlgvf% z&f8ISc7oAR9ATMa?C&3@EGB5WbD^4AWK~F5MgK#m zs9EPa`B?0AU#+n{K1`ozA6g&2L@Ke;xLRCkYN2{bgL;iljk=!ts+^FNU#M@&lhUI+ zRo`cW^E8hU?2(|6!x40HSqXcIa0!MtN$>rckCfAiHt7~kGxgK;vqK}6Ez{POyp^j}v@_O+)`!1l%&J!x<`yUx z8NOP4_OP_lAO7^VzWP(Ek>sa~hD=j+8@K5k^YO3aUsjF%2BBT--3DCC92@t-c|Ex! z-*EA`MuVcy2`KU32^@qbm0p){+F23%fk2A(+eP@r#aq-A?DtldU#i!1z^4<(x z0X|tcY8^|VdIxqDEPWq(9dz1vBgw{F@!|evxALU(e_IE3A%)oC5@v`?!%bg5$;m=lXWs&43Elm43EVl#Z@gwQ>>l!dTk^ZKE2nE z!)&PyHK!VN9+VEn#@oN%Jylw&9ab^Ef3zRH;Xt+0w33-^)8#T*zIALke|uPdh<(UM z52DZD)UeTLT30b~-TqS1QQ<=v!^OCK>%|4*;K#9y#*C>iIbXgs$GFv$i5&u6mc1sgv=<0J zHUGmbN(H z#`G=vGGBUosxT(>(~`xmoVUWyxtHajPQ3pv4)@IoADO^}6YJqt_Y7@ygMu8l%P;4n z{VO(r+1Mk|`9=AstWW*pw>efkCq7RDOhdVT%F_gp8iL*59^V z%A1U|`|F0EQWbO&znx#aTbV|lE%@l7tTYdL?CLhNjAhrM(<%@Um z!9+vKR89_x?xlrXyS+%5joldaQV z!+IGY)9)507Di^K|AppkVfufd{ciaa?T>N&*&Y9HV!VnL?k3h6U<;d zng5S|{?YUwPX9!G=Van2W@qz)=`8pkVf}^t-;IAq{G(6J|Mbbm_5bSfzgqqa^Y<8d zl}()NtX+Q3;X7LkXF*nervFv_cP!2Spb4_Dv;T$k-?e|o(D107tMCSa9|z#BxKFH!AzP*Lqi%# z6DbTOLK+YpeB?#F?^F5F=lOZ;XG_VqDMTr|{f;I@op&e8ZmT}46{@q3zj)e>=xaqX zF%@B?g%N3>DeXHDfzQ%kiH9Fg`+LV#%7K&n>|26KgQS^*AiV*SQ>zKY=p6rL)3B-S z;Nk7S{>^~JpPbj6nWHFl*E4dQD1O%2^9ObUvtE>Wu*omJ3e1TM45zP@a=>Qm6W!r2 zTIk>P9U@}-H#H`Q9%qj^#9FxN!`sm`iQ|>u4On>aj(@r~1L%p+`q!M_CKxt`E4AqG zYK>RF3KMGhdf56Gp8?mb(AeB7!!;ju|6-l!Gb{t3`y15ZZO(CV?iafoBP*2F+&kFF zkcb3gt;Nt)Ep=0yhMfPRfc2Y#Ld{p*%0Gz`Im1SZBd2?xT?hz#Wvq8^{~~u%${3K) z6R~0A<;kF~+G2O0n?EM|dzOBWp$Ahjz{v3SVDHa~%Y*F;>3|saw%HPRxmN)jz15V~K)c0mgKxK_l(h}Bu6Flmlf%;% z^ndi@kBNs1L(s{>i06XwuQ`Pe^`~?tO8a!ENgd@MNsuJ|e!TF{*@kC>j#=YQ(-X*U z{&Q3smxlZ$*ME&ExuXoBtb>Kl+B5zyjKA3amqszC{BBRtMa_=-KLF@p z=}T9OT|z5O+O(H>%DzCA zb)1bp;!j;f)gukBACmsTB}+0_E%fj8_iru!t$G1cl=_=pxt3i$Uyb==-&(1YElhEc zQYhlp@=>}6mzLC$iUxb|-y{{lU2DO>zeC!$fJJE{++#uqU*@&c$=u*!h|})1zj zbeh5@=)i zsQbfb)d*-)xw{z9L3KnBmAQc;6(Dy#Rb+QY*inNMN@_o97}(yJwV=+Py>c*%+w2%C zU*b-t2on1^g2ylEvCKXqw8MP})vEr#_c5yYj$lM0AA#k$Mn|6d!8E1U6qAOe8G#dc zAT>PbKy|8v5WpK6Q$^-Hg3f1rBoXK?`GoE=TnSkWG7&J~n2VNjQW6c@r5k0y9*Ixe zHZ|hGKJ8*q!{c?t5vEe(Xf$m(6A`@;Y%F_y^j17VOn^QPK+6@8(pE|-AFu0TkUs{+ zs4u0|+f@GT?#|MU_KrZU4j4$7#YnRA#)W-|j&4-@SJ?ltf3m<*f!F5RT-d%=dl7P$ z_&{Vsvke0yGW2;PZ*)))5P^sz97-th?C59B`t0BU!=Q-ZHNnjq1Jz)ia?&H}{+*uY zAfu>SgBDI2(E%f#uqgy0{#+DB!*B%jmekTjTaY0vHpU9ih<3fEr#;>?s;hoYR`l@G zqlTy^E^Cb#@SO|YtpcY)j=;lH5i=7ya_9K^19jQ?8Ec-+dLWilszyf~+ zq6dWnimlR!op?z!LDRgD%fan>Ak7xHgms!k0?5phFrfR!Kv5f`S`MNkj%%LlOXDRMcM%102XC6_T`aq#XCVAEAB8yZ+fkZ7yv6lQ+z&jkU4GDP0HAwJCPcS7sxrzeLHwdp2$9yWpX9o6GthNs=uFDEEd{!R6iWb zKTG}h(fsiWS*e8Wd$$(Iv#L0kK36QpXWuNEVT#U;x-UASk%~>l!=UwoF@WeZoJo!Zvrr z8}%t(H?tZB^+%NN`y&~^BIPVU^ao>3&~Q6{n107f#oEKa8y8445|1GzmOzNk?lTJH zMkcR6U19W@(_^)=_GW;IYBJ4n9In_#NJ-99oq7k6{*O)gUvKMSGx+*>&-#FoPxtK=NBOC|lx6bCjfztsZ~juFn7@G>MM6 zVw$u8#H%knG&GIc(bag6x3tW6U5x#oP?yf?!1~OT;=v!FCxRx(CIkA1xnC?!Kh9leJCD+TW+tgEqtkQwlH@*)13^vzPSp z_Tmd`$td4bvWf-60I&W!X;Xjce0tcbSWo)~{jz2f7X2GtBW{&dcm-FB=K@hEZz z4^PpQY$F2f#|<7CC~>zv-b57tF>odsTh5U<3ECX=xEm<1sy;J$O>G&`E>JRC> z*od1Tdt+J@SQzLcEJO-J7Oc*-GyClEfhhjr+DLr^5O3oZ2{RFg2ocnj43bAgZ5}CE z0c_!0@BoHGN8jf}qTWE5ki-@Qs*f#aTlS7xu@Xg&@@Rc!=t%h9SwJUc;=MEG>W~~F zPzJ<9ecwvKBzlbnqo9)p`Pi)%wkn)u9~KT3iN%ne&dAhQJDoZNIW9LTUn4;pEiGe+ z|BYc99VoJl%F#BRY1g>45me5>BQ?kZ6BbUCEG$biDC{?32Ls*C6PH{+ef2~h&kFON zSTOhG(e2E$_aEyi0Fi>3vGT~BKb8~@Evz50VRqCUPQuQ9!p@tMh9Z2hEUKI$G10IP zW35256oQ|4M+PG*0*lrF59euHl!#wMBRi)-OZlN-P8eGR#12+Gg4zh&7>WGirpWnE{*tPz`Ic~mgd(0TPCE+E()p4E>Bq3MgQ+PN)Y?yb5LWOzdz;@)%-Fukw)_!SlP*Wd~%vmUIz6jd8H z2@uLKgSsP7LV}2TV;QDYi;OGTAYVGo!~&yoz*$|vG?>;@?*k)f>Ynd{w!k^*qppt23T&&Qit^N4i~BO@MT;46 zG|SAo>tj>7q8q|@(t_0`nh};S*Ch;&Q`hx?lSyUlO>MiRWZ*XJ6hytf$bBS|(bY3_ z_u2WBJI~}GCSTidhj_9RD4@M}FO8*5fC` zn@kfVkEV7^k98gLcTy=j~OaHJDMM1D3N}(7MP>dDGdtQ)JD}{|cp%CpZDk4eO zj}dLX9O_!x(wbRZ>Cs*4Us`u#<`uoh$ zz`GAjtAXlzJ}&l|{1{4GZteGPhK7g3tXJzzkQtk9FoU~+5w08n&bG^KruDB{cAq%Y zy&?#1Mw8k_#KgMef1ZPJe$48*3E(j4@*A(#L~ zkDt#TONQ(EiJBWe2Hk@(zTF=;$22<38HBB8#OVd?PnBjq63*Clyh0cBpZTD+c0^H{kYS7d^*{9bM4zOwxXgXE#l3`_9YLv)1R{HN25* z_I|D~4j+1Zgl&9j^Ax1!eUaP7b82TC*Sa!a=lRRBO1tSi8}$zIj~4^8JnU<=9V5IE zcY`Bd!BK<=Slnj|3B4^^exUoXqf(|}a!`>;5iP|QEs?Pf``((~YBptOzyKO?Qc5zW zgdGGM*q|y=$G2|M6O^k&IL7dNH@eA&auVm_3|g2DqUcRlWf%lv<-~1Xhmk|T1>toH z$=jBj4_3~s1nUMY6ixmLY}J&bPR4uv3Ba0o_dsNYv-1HJXTVv&&?{_A&9&iz_uz;u zDS`DHM>)_(=3Out7?xzgXj_th!ynCm)_r_^y0aZ)!RwpLqKNI#?e5BK(oe~C?Di=) zOX59CL%`dl3l$K&;PdVH=CIx!t6G%?3wS9Wf8dJ@xF3~k$$#AE*hwNh;b+&j*Xr|% z?FxX;?Wo7^ao0~OmF%b9^R`}CIAQ$-vffQ#gc^oOVWW$y>qh_eW25`j+UBgDpAVPA z<^Xvd5$?-^1S!#6?2NMQgA!TmOEZh=5i&R9gouAl9l-B4G{7qeJ$_!NF6h&H9@R7I zi^dNvtj*%H`)EL>%kcR4sPWBseIT*398CxjCE*jceaH$_=W#&5@QxH1!(=ajuR~`xbVRvQUqG+W2282gJq9X-(jGkzXE;8D(BATy7v<2bjN$aB{QK97@x|Bw2 z`1QWG#!w~@r21U4K3om19#=1}wUzqb|G+paC@)8RK8<^3Zd~;|J?%y}ny)vRQ6Y!$ zKR;Xqjc*4O=?{SPKo%dHpT3dZZwz`m}~L?bCH+*&k!4uMQ6mUWWwREPh2@6}ZvvsU>+j4U2b1i*UUCQ7XvM zXa^#O1W!!QYTJZz&~OA0fJ5sXIA7p^R>O@9G9Joix3AyVWIL5S2LPA zfCZVTUjIu*k==LHAOM=K`zsOm8(c#y12SaaNPu$a^q@3t;(Nma%B>t^5yZH0=5ORO z>!j3USwy{zw6s7XB3TuHiel!Hx2=TS=}PZKxSm7?Er>x*3qqZ~&;@)&;PYCem_k^o zOD3R;yzLE;lV~Fd5S14n-uXe=UMo}y6wNwFl@NE;IkOt%ZO6qL{V<@+PphcN(#@0J zwqfs}n@mZgJJ*w2*dEuQON=X`Hq@`&qJIN#7{M;~&>ha42riJsT=ymK0aI3hd+=fh zp>XN_yBRLu+9tU?2l4s&%Vlz+@+^;}vpFpOY*;T}6)>5kjTLY;zv~S`7Yyh6(8l6E zQXn2>|Jwx=Oogvcd*0z{v+#x(vpoH@^YI$+)ntw;FM7wbla%{VbXGufOhHUIE^w`V zMn&!pxs!3IEGz4vOx0FyvFIX^;f-TwnYOmJUMjtapdS7*s~eSR*EWBndbCeEEC~i> zUHt$HTdvGlwVf z7Md-H@6dDSiDB>!oUu>s0l&;+&VN9Q$P5)cEsZ4Qz84k%EF{I$zM0>*A=}B9g+EJ8 z=@@1?+Q)krB52&a5N~BCp0Qcn_`To)ZWKZr9DXhSnWX7hzG%@EG*$m9WQBOn@R5N~ z#ts$ftwki}S&m>&vse5Y3}2sW1b06~X=|W|cBsfnwq}sL*H>AYQ#_xdw=5c{qHWyy z!JruTY%Sj2tLcg#E`tItIm_MQbm5|^4b;F;DAmK(lbpBwiw@{SxF3RlVgs^%aor!r z;|(Yyx6Fi|@ddBy;zT9|8C&*5AI>NAGE|W&3&U~U9>;ZNRGg|;pZ!)>-@X&~#wPaM zBYU_k72=>}41z!HRDV9Mq(bdkpxVbEdEAq%E)BX?y9@lXzMZPKGFqhS%|K-oU^glv z%cb7(rAzx7(iK_s-Fy8)kGUXL6|XL%R^YgC)Bc>6I@757XZOkC!oo<&-Ce?zE?C?1 zcz$;TFSqS>sxRK<1`q3upSoh@K!u3@0Rs%*;EyvedaLQn$pEduuU^Ef*rX@K;iDJJoTRg3t? z?MYF6Kdsb^7Bjx=6@&%dm=@u0udfA4Da&oN`V3tD0?EGW`G@f zI0+P;WJi>nLg3gu2Mq%=iO9KFf$vy>dOeq_J&{21NEC9wc@=Y_@oI254)8te$WVSZ zZ6GmWpf3{B6%l{2NaN73z7a4Y23hA@;v{;oQ+^(~-+Zdt$f$MN8j0V-CP|noS;Ewy zsFOQ55Ui!`+q*4h`sqWBio>Ex#gL!hb-Tmv8#f%cUPS6F{Q|sD>#Zb0yKfFRR9Kx8 z%fIBOQ}mwiX2tHp%}JF2N%OwHD)rK*-gqt51-;Ji-SxY>Rv;JY94@MBxivle=;qVn z`g1`9dUvldZl$^%f$DnIuYI1;f-$iGba6Yq*&c{Mzp>1sWb+vMD@ZKLZ6Ies(<~bf zU`CN5F1;T6qo;e*O#v0Wq3yX}oF@G)SR$jw_U~Kl+8Ra2_U+A=qHs)`x8W+`;jC}T zhiSr)Bup9`%|p9ma%LFW?dA+Y490cRgvd7F}>{`yftxeWg*@y5#Ff2obU<;&;a$3K#iwN8_<4DQoGDO`1qYd z!*L`JZul0B5VGZB<-XPD<<%3{tv)%zFzdx8Cu-w5n_`uLF>%><3F4KiSGDgQdV%v) z(=N8HKW6~FMCEjj2NtCpDXDsOXk4_kaRN_i05FB9L{HF$>x#!Ns;3&zF1M_okki^f z*&_uZJE4GUt0inq;72%NVg-lS)sat5WJ@XW<91=mGeD9kfG2EZ{-*|jwP+zW+<>X| zCeZ^ml1~b@*CbWPv|>5B)S?uD8vXgfz^P(#Qh6kUqw?mn81eAZ$Cd->C5QVr?)$~5 zBif-+rW=JIP`rC=mdllM_shqw%c?n)fN=NR0rbe#H;KlU9R(WTA6L_^2StQ>kQ z@XqZj-)fkX*}6y7#(_1aXM#JsAObmCtAVuN9GTWaE3b@f+p3tSao}zBHqWvkn8&yA zOAxj$jc@Iiwfr3`a}aM-1Q<}<^Yf}U(Hg1FJkggBsLCBj6q5XEdZtSA(_*=v$2RuB z&*hAkuZC!v)+n^dmQwF{o?9+PH(ZKX+HObf(4^e(S9VJX?Fc~j1HMu!fK{ZkO&a;z zc%K&&hr%ssrfM(7D3-@GZq{LxV>XQj`|NeXV%t}}r;XJ3OI$#V#5YFTS<94Q>?h3i zI{i)fcfX zYyOb8GTxF1p1_oc4fOTYpqQARw;B3mYrGW-Hsek|PcUT{Hu;63LRM^ck7nG}8ogx? z`euu*B4N%ul$pH&UOz3ARTY2Ui5Xy>246+|DocVv>ZXfUoU{i+Ek~mu2J;E;Dt>ll zaS6Gn(@5l?Ngx1nPF4+UY;SyO@PIeO%uX;?M(j6~Kzv}h&M*?S8{ACHY6!={ zC*DoOtkvFY+5>D{@~)%VFKHt*Im10LJP+wmMHY4-(dy>V4-UM7k2~YOkbN!_F8Co= zSJXih#{h%<s`s+DC9oOu6gIc>a^Oi3Qw)3$XrB4q5Z`w@{jVq z(@dU>>F(%si-c$!H+y47VO2tgOJ@Xr-TJ!75>dgVKrC&n zs{^r!2J^@H4D%;ID`DZ~cl&M?DK=$^&}8P%ZNWzM$MiC-Y8^68-Oh#t(!G*FPZ6A+ z*4%ro>xRZ~@8I1vXJ$4p3Xi|fS6wCUpftt#OvzddvanL-Zr#XoNIAQ8$guQ}Ij1m) zqjHI(?l)MduzBlB6x2pr$ND`U=Rb!BA*%7Jc5Pss-TN1p!9Y`^wa9{Xr(Px(Lp2^?#Yewsu#&+Oo(XRasuFn=|q$r$4 zIjMbT^|YMul(rJvaeE#JqXM*@Fl8|%&-?S z>RCPZ(2*|IgIZZra<`{s@Z(Kulu03MJ2aGZuri|rn+ebJ?mFM zr_Vj>TyzOO_x2AJS#Yd&ZZaH>krr(Y{m+9+_@S}mVT}dJe;Tp`Fh;oCG`1~Mj@qYu zd#!6?wZRgZvV`U3<#`a{Gmsd$JjrlBt4q+ZcW~qKcsqCVJT=xw6L#3)c1Id`IC#F0 zAQ~SiqQnJE`@;J2iJ~O#y-({!d8AQ%5o`JwuVaMSE(h04!Y2$^W9}AsVKwAus7wnG z38E(5$Z;1%gwXj~fP{{QGz2u_?`&QvXWKyP>ZLymMJ|<8pN89uprc2)Lb^k~ZQ>Z3 z@rm6S(cVlwmUzn0bvZU%EFsRtb!eI+5)*hkBt0w(mogzM+(P?wE&0s1rKm}vMO9HR zCd=}CM)C|0dI>0mUDEqHPQO!MzhAu<>Ke>#!E z`bRMRA4P1mmuAMzjo%~5m?-<#nr$W{u$o(z8CQg%!yzI6dXTvpvsqLk*CmKRz+5D^ zSB9-sLK&Dk=#m6<;4p|^_n|WMW`F~I@m$ZU^AguF9fBr^Vln&t>{+tR?aX?CEUYHM zaJ^{dS&2Jcwa!;$rw)weI0QzMYi#|HXo}u&SUJH;+L9icz%5Hc{5qu&h!^_#I%7s0 zD{s(dW11j&0=ATz=HL|YOQ@(!R%mGx^OUUyS)T!xwx)cT6g3*UfJj;-=4F1>H%wHy zUwxGwNiv|l0v5Xd`m+q1qr*#2o@?dTNv8zn5*7;>2Ijauh%HzFM($W~h_nLVN?G%| z@3qEFpr58|d|kp3$jX7M_fOZ+7UefQ8b7y%+N1l4eXJXX7-oD~K-eTvUdK)S3+%?) z^EBm{zLAni(^ZzFi8!oI=o8fUe5t)rxSP~Mw-G!?B?_y?-h5Z=p)JIinq)8ur}J8$ z8X>UL0k(;Km-*;E2nvF?6vTcH4x1-rhFjR`+75Z8CA)v$Dc5{{3n(n5pzGe8J6bn` z+%&ODt!n_OPtzZf>=jkM_S)jEJO2LnP~j$Y*GMd$NIch)$RfPu1T#9RkSR&BLfC9R zzJyp6RSbat<~Lef_jJcVeV!vSc=}SPgeSauS*<~t5KK+Q#q6pU5LFR7ib*mghnB^) zizYoUw;DcuhiqlZ zs)YXpiF`?weI^TX@qT$`IXx$QZa6Z?o))(Q%{OIvkjg~0p7_}xSA1<@;)k4G&EO`RQYbY4~bSVBk`NNH&quqmYNbk?7O!fE;%PR6G4(Rc8a12 zhjkm&hOD{+HC{;9Sy$$4w@7}kJT&E75aK%E9YqJ?!iyJ+yJN85foe zyZp_C{sW^9FP^wV{~Ncop*NT=q&8RrDz?o(ziyDpQpVT#Jsz4as<+)?KkRU|i_~?N zF}R9v;rV89gePMQyu{+p?q5Q#3+(Pp$Xn@~KyeoCJzZjr2|~j}9;=zE)=O`*RPcL9 zPe@B@7*~0)A);Uu&Rf=Ehp=n>{o9tNi|CHCQlUpgvn-FkaHHWvsuWXj+F}v{>*O3( z(`dKfl)9c1$MIvkmy)tF>qlBaEFQZzT7P@N1q9{3$Pg_OeR7NGCkK$K{sJu|n=uC@ z4WW}%#<_{!iN`G0^+F3H;(cXXQXD>28rPS}8HuWSXTq&H*wf7pOL+f7u7)L0I99&E z1l7gd>af6HSVgg1VsbWsVOZ-M5} zZc6xb%SULPH&uHe3wofc^24zEhl?a2=Go9GvuBU#b>XLC24?ts8Qd+^1s5#|$P=qpBFOeA}((S;&;?KP&U6ykQCaA}vz-Y;DA)VYY4b zo&}LSbb#x^p(y2){VBfn(HYU#l=rUkJ_)bnIu6n9yblp~F#aBpm6Y=j>0Jx{HZ_~^ z2EQ%Koj{$T$DdaaKmD4KfA(h{MYq9h?PLy(ogQAlU>*&kR4Fcq%*-Jycg~T>^?Au4 zMF;CgXh`uGB#Q^8Knn2rM5n))Tl|#B62vn>`w%K8p0L|SR0xh9hf|@~mi0TcT@Htt zf^0mp<7_weq>{^#k@uZt%VHr;vy;w~NLGav%k0OHZ3H1Omt-Io%CQnD97r|Z;O=D! z4M31z=bpaTmeon10rq8(D(ZBeH@t+n6-Aidm3&75$GB@Ua>^JfHzSap;c2sLqOx%G z+2&e&?C(avN?naw`%GX=`vY$e2u1@p|GZjcc3aV2^61ZQM#Lr`l>{d&o-{6l5tsa) zAN#=djjLHaPWI$ejFF4KgPdW$cNjC(TIwzHMcw=trDkg;mt&`{8xhnQtfkA#%R$XA zE=I&ByiOK;IZ*tIH%K1K4fDxte5G`%?WQYAq9fj;#@z~z0vocB!C`gU@ra)019}p; znlp+&VD_>E5ih?4eS&n!H>%0B{Voeh3~uYUseVakGKampemkUo=O^bPU>KkH>^5B%#XP zPq8r`0olzYr2Jt6eN^HDfCBC*Woei_RTdP&ABQ#3Bas_VF=g)@e(HhInI4=Hl-|7t7q2a_2y`;qIqS8x|=E%*hcPXtEo8Lw9ppuBXWB-6I*jGD-f^JJ+GYxS|_qVsuVk0jKgM^)Q-pv@kZ!;i^5J18MD(ti zc}7Hai3+9|ufe@xz|%>{N zo<69YjweR}BnEaLaEuN$lGhhcMY{{_SsN?C+Xe)&Wg4r#KK!_aC*Bnvy-*k{!iC$~ zL|pP^&Iicp9lm8DKcp22iphL>^9z-uEKkU6_K-;_c(7YsNgOs81ul`IhIZJdSXO{0 z+lgjqmn3Xc{tk++f-c6aACiMw=#JRu4T;XG{(&ge3 z(|5gkXT%;pQNbsM(Zy z1(UXv#3zp^5+4ai-$8;? zWQOR5HCaLeyB8W{(=~-!nY3j&@;OD>l;G{EK8li0K4jtDX>La2Za8=BrF$)e;0pk)R^J@DVYf`Bts(TZW%K*B!FA54i!cYoQzeFxi6;B5gzsI-W z^4)u;uYgPpf=r5K6UxP0d{7IyUQe=>Ov1b?K#%3w!SldIgJ`|vLNOe~sLtw!g9j8kEW`_MfrrnH%CmGNk8~at>dhrK}&=mVa-UM|ao`x2-6t7JQ zGMXwgL7~e974SygJmM=teU)YTY-a^^l`$rRc!wVJ4nkfrS0o*ED-9l|1Sqr@0x(X7PeHkPa7|XPqj(D!CV1!bl6jwgUxa<57oG4X z@lH80w!k+7HyceNu4uWefF#`Aw0a;UDQ%c0Hee8?=n943 z!|(Z_Q%7hHwc{m0=DJuy_)i6TN&Fr~bTlFoaT2(x&8VLD4bjNnB6tSMvYi%mB%{*i zW|*>rEj*x{V)`_v{Z69U4?&a9@nMyX zN@ua+RnfQJvofK?EotYzz|V+e4eTkJF4`9#8loj8X-RR(Qh>i}u+9&Y%s_FsA*w0* zJUL>ez|?o|(CjY5e|_X6#;EfkR3be;MTRx?zs_1Rn;=GKc90d*x57o@gy4CSDpK>U3W_vfMQ`o5L0JOP!w!{%;)Spa@k_qG z*pm4Z6&663&(Lm);|6eBp|MgJI^*hefQWuK2;p=0x}~;}^n2OCn~UKN(FcuZAr43I;hrmPd~!6=$a5*LsRpoZ1g3B_3wsdH`sGq_5qhc*vI+vH14B7b|lS`QP}um63Kwj3Qd3n zg^?+4Lu)Jd$uFNoU+2j|YE(5#z|=Hc7!#iNlWx0jtKdJ5PP}|sBlLT2s+#Ogm`rq? zWXaFqd}`SqDf;26g8K3VOA_2^nSp8VH;iu8%yKrMpPfD}MJ9=g##_)3O9*rC^^&DX z^%Oai8U}vEzhwv%gg=k=tu>nhzTu}r1y>|PVl?5&g%clr5b9K|a~7?E;0NsOCHatm zjgaL|a}+HCRe42SK*4h;vO^^4FE#>$LK4ZAhS{RDqB~CmqXXF(fskbSAP5~gN##4{ z2blC&oEs;g`g^a5)ggD(vH9-96u71^QTfo&G9AAA5q^Pm*l&i1G+&Kh)3t0V^9%Wi zdKZWH;-_~>GqPXQmbKQLgsi?C_`7a1&BLwvA3N=)*zcG{Fhi{r;;f?2D{Ve21BKEK zQlz=Yxm5d*^FDmw+ar4w_cBIg(lH1+PU^OAL^Mr@f?sqM+dvuGor9`+hx!0%ivxKJ z`{mL6=hyfW3fvw!>?&$m*q9+Ab$Pk8R16}SF_M6J@G~+&rnJXkWCtklXlzV}E@%pV2>mJARs)Z+EnHMXh={gkgpEt9Nf=w-V^tPALB^?O zy&qQC;hZ8HnGp%jZ^TPkFLvMDUSg#xrpz3w#YADscp_Jp3cZA@Q8WL1HymC7W^qap={tz~KLPK%A?g0D=4 znnd za#(gJt_&u8OyZ881{DhcBXTdXjz4wSA-p@IqKa}o(Nk%7tj7c)GHz6R+1WDLyYJj)DXK~ryG)%%ez;Tp0$$zag!9cl z1S662#euVAosVqF%4Yb()Un7B-v>$v@geZ;=z`efR2T!0|Qm5q2oga3bRlW4?F$Q1A5zhy=4?yFR@TM@8@Yv`R3Oy_{iVYC}gcktHGqXv-hYRkL^|dGh`yYlFNyN(1@G|UR z?c@Z(zs8p=Fy}Lm3hcEJv{HHuMG!hfeqTs&8-9bo@hTXSZ)qInggW7bgW6gshUprTA9bul1YGS z?mu#4q05l4SXC@OPZRF}YPNgif%*>3QYwiTm}gYJ&MO0XW>eQYXlJ zT)#Qq=17zKLqsAEs5Qc7NE~sP%e(u5-3i}lsVgL`Vp2ysxcC!|6*jr%i(1mu6`ZV^ z{(r#1{a*(7kTL!qVS>AvYT+gGv7!~#cpMRMpAAaxQkP{m8Ayy!F~s&pd#9$s$$YYc zA64FaM#%|m*<8im6^XNGQ7(R&vq+j^>Pbtgg`Mq?EdypO1Vu4QkVBAL&*d%D#h;S9pdREzD zfn?+2M3nc?AA%Vpe|4-X$Kp%s&|<|<3QEP-Go=kn!77R-4s^jpGy^#LjDE~I2q>$f zSzJlx8DHYQ`T|fK%$UebO20Y7ZpidW+H409dIqeJM+j$#IF*eu_x|#w{eLiL|N8?r znZF|n7c?nqRJer)!K<`JH2KIVw3t>{7)d=M;(RGy;1u!r5cHpw*p}wrzoQ`{`b4+) z$JF`9!Pq`)mcuw@xl=nafd2coQ89Gb(v0|0cLjrX$rKt|Q}#uN5UJVOS?d;YJ^e8$ zdN!iN$>I8dsP1d|%#f{rdgMH7yHkz*@K7*qhg@PYAMflg-++7^wVvp08VLb#%F^UA zf&DO>!g0UdQ7bA0G06DVQaWV{RbC?%U=6=T++2itR?e8;<;RXZiWP*hN>L>IbjNA(L+YlnISjmsVC8e$H7O_#X+p3o(NU@K9L11WZyNEITn{ z6CSVGW>Y#enPms%CI=@0y1xEC*a(B+kXI`_*D6g2PK0(Iobq^c@{oIHfKwfmbRQoI zJU-6vCRehnV)y=rO3er~k5@B*d*W~8*u-vpg8R!nlCsQ2S#K4jUpe3vfgbKQa8k4w zVWnjBB+`l`xy2`Or4mB^!i3H_7yzL$vLqJI!qJuV_I}zQN}@yiHwq^PFY2YpeRE2G zgemB4wI`X=&PbCD+_&SK47K&a@!V_%Vp6id4~`bpwB!2UQf5TVEf)$JtHP7+b))w0<9g zL}gDyVA+xH^I~EMrUe5sdOM`5&7CCr-~cc-)K9cUK$q^olp)3Fj0smg~X#;i;db zs=_iyYD9|F$%Z}IsA7YoEP*B?e8ZGLI_gyWXp%*U>6l_&{B33voEDL~p7}BmM4r^c z-IxT~a)b03|ECKeL{%Q!v5y*=8QDe5^VB|GsG2HLM<0>~^eZ7H*>-y$mzZ>7knlYu z7;r7&X+NZmHbzF_#Rt6IJELKbH03x9jZZbyEaS_fM6lP}Nlzw+X=>VgmE-%m~gvcHV^nmDt1qt@aaH^b)0$Oi7nxy`1|g~LuM7aPqB z1CC`LhG%vEiJEv3dFpF~=j31pjb}-$YF-8P2VuucjWhikoD7TUl_6yVQd8Og+k$bgOkyy|OX>~(mUBwC zn(!!^xy;8Cb^H+nefa1{=_@DW@#CvI*N`WfLeBD@8eHy);N(cJrq3HR#~YbQH0+3Y4F?i1R{6axnsV)5%sXD(P~}zh z2R|}o&TO@3`w%ccTjT9k(FF&Z&_{JN0i{yrM|}9g;G0whouVC!{2|9I^+7k zV(R~QQB!STv+0h{Nkzt!`_@l6nOi?=Mh21#)6naoD-gJYaJ?y zC`mDIVRtDWbIf~m4UNy)iUjs*Kd9kPF$^1{lhRF`4rpQT2}w<9%})&}l>dA-e@^{+ zBFOUoXIZArguZNg2_{J<|cCpckQ%d9lE644P zG*|Dll7{KLGJ$gv9uloD9zqG~iLcMJCOzV2u{x!#D+HsEDKFJ*1zFOQWfqR^@8M0c zm;;G=p6=)V$Mppiu>r-4Q`F5u_5$be3P`9c6PoPBbK>#gA_&tVgAsAVctomTlM7vk z>2377zi-k*oEMZ+Nf+i3V@Ne{|IxHG9hNL5$r&>mmu4j1dBPzPED$D-AO*=g1``_d zoru~!Sxzp`M1zbZHAPWM30d))4+*(w3I>g6ipZUU-mt&*kH*u}mHu&D>W97`Vby4^ zArRA6YI_Nk=s+kdEBj>{jOScoX(q)ya$LGCL0#sL zxBs}b=>I>~yE=tIYc8mEb%W|Gf&U{^QZ#-61&fRg-fozAW|~ypfJQR{;EGJfIW~r~ z-YZ2D)l{eaOwEKiI2=J{q;ghjX&$yka0+!N8x$J3t|6bIh~X;cUq^1^!5xf3bW4AX z8}UfISdS`Bq!jzglabCMRTNkto>Z&}lrg<|)A&@SO|HYYEti$*n`;duBov>l{^cxA z=ku};!`wk0sK6h^I$bmPs)zOYhcdk7(lM!Yxu82A+7!|Gx_`-5V`*c26isp<-yyN6 z%J_+7YdQF{22*8LyRf|gytaDN?*}}AX32<-!)>}nUhghL@h)wl+z~z3_jz30Q-*$6 zR1TWzO4K<7?Z0ixE<6#9!LomCdD|yhi06Y$zof+XZ9q?jUq-ZMur)RaF@p- z`!TjtQynCLA_zxDpcK_~Vsp;sgU9gG&>ctvX+}795((Z3;IpEA9~~)_7~T^*F;GIl zuNqX6Na;O24Gmq?M2iCaHB0O7cjoEjD5tg4Mh5h(tw5vC08IxgZ60tnFfKjrUG?QCc zTuo5TCeFa>AWc{>|;%W;ojse$F5_k=d#C zlp<)4c8pk6_&+YjDF6GpU`IZ3*bgif*EUvFq($-UQdbKC(Z?Hxc~MWUL`jW>ZQd}M zn1f65UGE#Eq?{fNPoxj(OfPxOj+5+oszUf>VtHD;SnV@m3qkxq@k2>J5AIS@;y_ujOTDJ zSJlk)pn2Ibnx$I(Sof@7xa;pP_lbsp1P%%&EpY8tJ5hc8TyF2FENSgscvh9xS}+a<4-56YIFiTtU2&di6gh)YEHltAz|kN)z{ z&$VfgDdKG^n|JoFijJkMtSq(peR)e@YOnw21h@1Frd4RAf2_dSV8M#C1HcZZ$ejspi@nCA%JC z^qIZR3gdhzk`g$QMtE|Mk<5R2E zi|%-J-c=dG&m@^z<6c&EHHryoyxn+fZ_RsTgB z-*FHXW@yNE;}5Farb}viWv99QpWP4|_N!L4&R|ggI4*rY7)v*duI7)C)TBKNRipcj z5#Re==;ZN70UoB|Zhvs1(`%}}4-SZ7HJv)#ziPLx?QqSV=3U&@fAc2sI{gD;QQaR` z&1GZdx8E(ZKn;Db)9Q{A9)F0BbmzLyAFH{`&CeZ&!B~1>+4g9M7Qla$A4+?kReH`?4W7yN7lCqQC?dACp3RB;V{QP1>j!+ zDtVkCmH9})Yb-^9Q9IOywPNga@sNBcXVYX#d4Fb@5Vpo}|BR3A7>i=G$Mq-27Pd4% zsvZidsH)~Q)MOL{Wd{W1D&kxI7F9lRP9;Gz2~HlvabPC|*VaU&D|N^5+AT^`$oqrJ zZb?3W^rN@RSg!y3)>!6|g0v%Ps$7Af>Y$|QfT%JODp7)rVYq_ejl%3GlM=a{BU!vr z5!Bmc^@EXCIr_&ec_IH`S&|V{As4OmM;SdFlNDKbtHzM&X$VJWTRqgdn=30%uw99Y zi`Dr_W8Y|qgEi|~Mkcpih9XfyzlbT{r>%X=8K|hy}SHReq|*>S65deOY>j8G!lo+Q{4^OwFie(c&S>Y;6hT<=@S zrkMj@(*N=V6K*W1JZsOJI{f4R6;`gdNQrL%`*VLjVRS`t&@lq21Rco;Sr0Y6lr9~Z ztw=^THwn7RvRSbVG#7OuZtwwkM!Ftk3Sr?EQ~!SPtH5bXQn=c!gArB60NTCMQ~Jwb zQVb?%j0>Yq$&&R1l*J3eTALfHY{34}W5vi3ndN%&%cZrVkYuDu6^Z<5gs?qN$+y15 zgKfPKBu^3J+7F72r?gbFg;Q`Z&$A$zB{Gb_@yQ7EOrumjb2h;3VsCkJSSKdIBmIkC zStL^DVos#xmDZo&#xxfs$y$YA?e@pOm`egfxPMP~lKyZm6tat#1;7bkLqEPW92E*H zAWu~kzM`<<)Y%$ax><>1-$o zhA!fGWqnSY`?Zbo&skyT`X!*O@<;G;d_K1o$|5VSrWD6$69!dqI($;&eU+-HX{Y|} z-IM?_A|j&MO50sX$*-uVw*NSk2w^b2R>b2RubQLhOQ^b{dew~8bEIHSsX?vC33gM^q+NL?40{GxkW!sUC}yqqze z@quH^2E^*AbH8R9*ETwxD~?lef%Pg{Gr2 zN}x`{?Zh;J4ANB%DwVB4iikdhrh%FIGr!nA??;(B+up%&{nt*^nX*grWoQib1?cJz zs6$L>__$6=6-Om`WWL2@c*f)gl@3Oe{rI5wHpKu?S-?w?4E9M;aPWR`2C$9t9u7F9 zEE{@V`5JHHS!?`CQ6z;Ei(z7ik_m?M%!XwRE{PQ@BxD={b%A;hjTe&u%BJ{Lw+Srz z#6xvW9l5HCD}$p8e^(L!bk!@|@|;5iwbOx`&Sw4Tg-;S8M@Reimu?$&{RKmHs_Qv$VN?-pfA8qcC=mSr{j+Pd zKblBC@+l&k6aTa&pqGtWp{$fWYNI^Qxaa%tfsg*$scM{J=_#ZfFwo*pBu7-rNSh{f zzF>C0<&^)0$+I_p1B&)q_%aH;^-TZw_w>C%V^3dECq8T{eeT@1P5UB6qD4+TH3SZ2 zz3c!}PbQLk570r>wa}X^Jp6rgo`#?%#HqW$E3X9`t|_^=ZE*OXmFru)m2MLT>2{V$ zGSpox<_udFPtpO47~Bl5_965_TfBr1Loxp_8)Bny5pl>Jm*hhKMGbGZ(cc zXZ190Sd(V#Vxw-4$Up7h$HMg&p8DEDV65n6)?^&?I^lcO^ttX%+y7XTJ#v#~7GDCz zL3+Isd()thtRN|G`^Ef;aVGPPfoVtiJ|$+)LWlvDcvS&=COi^xvj7U8XY*yIxxUDS z7Q1`9OEy!L%cz3UHJoG;a5%bZrfaaq-62C*k{NXI8nXVMY-opVJa3+}^1iX($OPco zG^8nwRvJv$m4LPut4(0l(z&K0&?CkGQG-`+wrqF8cMLWvQ^;WZVescnB=I+E#@-wx z$rU*9$wqsmVz%`Yv-a^bB&v!oLY0D4Q^RpZUG30V9ZxvImsPTr?!w^R9)P~PN(NTy zbR^TSzQ99E*+Z;XA4qAb8TS*LgT1WAG$|(=W$xez$H^)jfQ(Gdq}SbMkve;j-@&lG zJt339=A^H&`dP&L+lqE^O5i1<_Wp;dIqZy~JZN1QkgJwY?tM$}^DgPS-;+LCJFLwq z&2goT?yHQF=rA3}3_G5AbvY-oOjt|m?M9oKz6DaSKfDsyY%7)!3T4NGT43lx|JAfH z)>KE=6um@#ViLNLWDh{pS7bY=B8(a@+i&H?{yhMo&%|Kl_NJ|Am=s{ z+WV4_;gKI-0c7k-WtNEhqrc#?=y7KgA_{Wj(Fn!FMNY!wjhe(=Bx&C1Y1ZY?r+?4+ zUzey=x!;JY$khs2!wA1R!+Clap`OU}sxSHpkNuJ`PaEsEh`DW}GMKOQHG-F1d&h{- zf|pegx>MvO&9R!uA$WgSSB@A}|?nO2@ ziH}T#d_b%z=MnK-5!1j5X$To+d>~~>C9^E1W{HCwr=CSoRfRlyw`JXK|GNPx9sg6? zboz+bYrWHkl5c^md2Gt{|#*qr*LfK$mWo6EeJ%&3qsKdVMyl>$4)v} zi-24k_rTXy*0~oG!v>ST31B7bnX{XVwsZA^_D2E8`b;MXY;*NP>j&G9A<=j%Z!Gb$o$X5 z^Nb(Q&s6*|Xz=@_g!Ep{I7>CU)^@i%?Ck7@k6EiF_-;(t_>OD0IhcuJmgc!GEs{7m z_4`~lI`w-5&a)t)OXf5!j+zu)WG{&<|M7_Q4)*w)n!D6kR1w_Rae5eIf<5Ec*5QW- z2F{-L zed(m}LH_6Hr^}n;z6W>5BfU_Q)Q!PdGuu+{oihHE4WfyR_DiAzw+#>6vb>2j90tL& z+_pTtKHjrNr*FeQCuois78eg#g}b#!-SA#)GJkrtgjcLBCma;M{IGbji#i1DOk6Z# z54TUZy0x`F4t%P6&tstw1mdb#xMNTxywsWEH57<+mTgQn+TZn*gm6n)22~aZCmkIbxLaL zMSz2`rt33FOO!dNen(E_9~!v#%=jf5yh=wy=FMgGT`y95$<0~AA|q70QK5*xx9 z1tK$-V!e~FD6d9bmPltN3nHfcVdnN-cvxR|3>H~E-*`Ez&95MFk$Cah5$CZr=LX6U zN(Kkp>rXF}!zj7rQz6xvtjJS`TXG;dsJzS4enQC3pVwEnLDjw=nl3EKvqrYXJj%h- zFsmdb8(IGeJ)Uy%$NJVKE>wsbff;pTpqged8hClmnHOAa-^j)Sx-Q7cEP+^s5n+f!!{0+T6vEAMlQUILByoGRg)9_o!6clS5-2!!p=(hG*f zQcRoHzwf{Kd`Fdw$ea+yELSSIo@V^O$$ecYonXw`;~$Fsy1pm69@qbzdpBxoX>Dox zWmo%osOo(kNW|uGu+;t0g@t;YA4-V(eftN$4@itNBo9M`fUdbAnaF6tx#19~DY)%= z?x(@N5|1q6fW2j+s7voK%J@O$^AJR{&AZVt;El7(Xrbw+LK` z;-AbM6Q4Rgwi$sC=2?!=AomMTVuad;IN>-}ZHJFT>Zx|8JS1mZ__ZTQAhEws^z{cd zFygIq-8da2DA}~z@*@4C^Y+*J>Gkk^bC_<6v0y6OZNqYKV(*gF^RFzX-+ySIC?`FO zenZ6D#bteGYYy;Hlkat097dF{vZpQ~lfJ$7<(d76bzzP3U2jYMY14}+g+ZIcB=vng z#W%IK=d;~|@rPEEMGusOCZx|($2NOyTM?GejkG>Kr=xMzGGlGz0}hY#k__42`*o-Q z{)Sm!-5?UGM347)syHA@#II?X=XR8=TA1oUkDs@SvLIwpgg9jy@#U5w7d7{}Qr)*(0ck&RJrzOj+|KQTu9va#+MUyFfoME_7YNFX)vX%mrrhO*w2Io= z(092+%y6;m=31cb;P&$E=W~zO=X58B)5av{wvErS1W%=^oF2bhP504Rm+WHQ`y@yy z_d2@gVZY7#$TL%uUHCztD!SYCv}H{i9Fi+8lGwWKvmQp2`_+FeuJc>&!2CtT=jeNn z+RH@evF{a*l8;{Z$lr&*;Lm%~KZc7_^?3HYehnx1A{2i7pzdiDdm}J?b^!?ZwqB*E zZQndUb$QI2-F0XWCrxMW%GM|zkB4Ggqe-|Xj@JCx0HNh6@~?PL1)tW&Prt#4=U)~U2K0D-mi5T*X$rqsQVRNrcj0C21jkW};!wO<$&v_PQy z6v2C?J|b<|AkWVqL1T2Mh-ktwIe6(ZgyU^tx-b1i3cD`#gLES?R5PiLhNi8EIG)xE zUcmP};{$OfKhXQUUslYct-c+G`FU8HZ7fccLYylGs_tV$Wz+Xe+)P}Uw1r-zk)F9HM zUVT7UUYP*>$#zIYfe{!KUI92QEo>M-h)04N42}3_ThiAX^zq6%U0cdHL1(Gq`o#pu zx!cIP-KkXT=*;s%Z>G+@CnftgKQQ#9euS_w_(@t?x==2gmqs=sApwnjw@)1X4P+as z*Andnp)T(gKh)%J6nn)BT+Wvkx`n-@OknB_a_Vm*!@((;HOdM2-0Orju_yQ*4wQrZ z5g-Eo*BfgEtwpb=vESby&+~(#yH);Lt_hu;F-yu8QJrU|twz%oZx8T$z{?y}gP zJ!#Ec-%oDf!=XC7eUmVkXwahq+S~4GdYdvLbUkS>hMd87M65!C>$@|$T3ROvTbM*{ z3mXIQP%s!Zm+Ou`@_W#h>?UHTw-*``a!lN3{~zJ8E5(uqqx@rO7w@E)T^=yo^~^F! zjWhbz>2AUP_-Wfm{Qv+<%hy2_&~gtFJCwIX-@7kMokzkM?jZET3{vF4L7b>DXqRud za@#i*hRe$~J&31y`~Mz@(11iAap&y$`sB^u2IpqKISqHs5I%a38o^HE2gR|^K6bKo zfs>1R=;XuiQE&d9KH0}<awB!+97zA4mkeWvX2h6Ea#;gLpYmAH;Q@W^=4^Z=Bic zE_V5f1>pc0yIo)5NIM93q)xXgrT&-l+CBM>53kAX`#EXG^154U0Wvb-;9Ai9bcO#| z9NdDWL=bd;@FlCJXfefePOo2ib_e`?T-FHEX!MLGeKEvr0{JXpSWvgqTDN#)>~`Gz zf+1yP^6Zx(VP5&YJ2qaN{6o?)or%h)y&4~i8TH$ois$(Hu%W;al|XHqTO3 z*vJO;yGNP!EtN_xkB5t^$16V}i44HNuLv_*pL3e6mt){>5iCAlA1c0vyp~NCJdU%? zyLBJRKck?41ch8GgQ4hspTI(*;Xm+eRnKBV&WKtp7Ut5Q8U>hV*Z;QNKM*tSFUUKf zs6O0hL;}TNU&Xx1-m=%O96An}F<~XV8JT0AU1n=5V%y(xIv%Q97$ma8(W%x*Rf|hY z(VOI$r}{~WhuwVXUj`nxY&pkbh#jIxcR2eL`x4pzB!`6^S&%`Az_eo2hX~UQ)jf*o zNYRTfM-&RL!dPy%d|Y@tBU3Cf6gOc%HrD5cM{hVu$yACn%O=pgD)1L$U6e>rvVTtg z6vX#V{u>QGX5Mcc+nlt=WhMF6{!mV69a4v&TrnhY_8N_+Y_+5}w$>gT!DSWkpa8aL zg)>BjgFFikTfd3}UAN8R8VI6lAEdkF zDP5h~nNQGhh{I{WC{a#{3%iikU=dLZ&40AN7fsDKb!Cgtf4?d zi}gi}B+B!Z`y{&il-c90lduIb!TJm8K5X1gv2WF=Y&v$uO|EXFyRL2y!p!jZLm~va z$e>(xp3Bw#<2*L!AAXy$g$Zov7~U*~R_ED->ypdc9SEgU8dG*L+lU0y8h}QGL%;j{ zc&iLYJ*$O6r;0Ptm>m(s`#>7wKxh*9?Hjb+`@pGlRd*IEiN%&WvlV5UcE>^}On2rx z{Ux7HzVmLHs(++TWHy2KzvdD~=pJHP`<@S1nCc3Wz|ffkEXqn%filv-iJ>tYSy*rT z9pJB-9_%;FtUzc0EE2358IXeO-Lf@JP*8hj45^zr->o?U(Fu5_t&HLL_EI%nZXTNh zMuCX|tNOlg?~C|uNO}8g981UYJ}=L&y6u88uhsqWs9ii3W1MD--NR1IcGCe47uy2W zaJMiox7fO7TfbKOhE}!!^XnX>aK$%<>r|(A$YbQ>N2c{Y(loCS#gEcYTE)w>GcXjD zc6m5l+&n&cJ!c3Li-NLiKc$3GI?Jr>sZaOFyBfTjx$xfFuFtDA2I%_gx3b^7iE01J z9Kc5X0Qs>E`0g)?%rPwC6Tr5AHvZ-4zM9|8trVmb~Af$^y&4d|_p@!LLwJaHh#zBg0=-Dm*j3`kPp z4M#hxKY|t)TWzpmx7BTqzimWTFjONU(8?9p^tCP3^|rg30@7AUdK*G6GBzrB(}$*; zR)-l`b7S1-^B<=VK`aShumLDY?ybJBhmey~DP^}X&=g*UFPy*9(Nr{Lsd;~)f4tl& z(N1s+N@lg2=wnuEH3g-7)%n3C6D%~@fxGZ_vxZjNPUagZQ(q~Nzg>I-OBjc^#5WUc zRd$^c5_Q-C5!@HgxSw(Knw?SEal=YIgqJV11lfzFth!ES$Y17*#)G9-$V3)1c- zqNDe~5|e>0Cd!Zh=%s2iWf?Lqi<=gJRr&o7$JT2wp~JHNSV6vdQl(T^*x&pe95@Vn ztv;eTw=61AVIjJ-jCfCnkf5>939}5u?M~K(vJ6yl0+4V-f)w+t;MMQ~{;v3eK}4n6 zzC8<`^s%WG)vZ|q(rixrx%rheZkQqo={?&cY`eEtnXz5MG!wxdQ?(3uabW$e&Mp{{ z649@BzbPZ!K|&r1V&HImp;W;d^p--=HEAhB)BPgmo&Z-us=WB8_B!#jaL)`|rLG4Q zMTIqUf^f0k>g0a7SXEY?*W?WXcBbf2Jk%ZKv6^`n?TW^pg( zjx%D50p;65Ne+E6t z0DCc0?w{p)K!dnN{F{G-JrU%#%ku!scphtv#p<7VgCLqr zJ2$4o-y!APS7%&eZ{WL62g>q1ad_0oz|oz?Sc`ywPG%{W`@_C7=csA_;B0OCuEkR~ z>`&q*8tO2tv?Sc(lHq`1p67ObVkQ$`V%Au z2-Q$3-)TvIgzR|)0LDpF$WU~HVfZjGUR3lHWbh?Zq+<#!ILN~HPG}r4C{#GR1C;Ov zm2?IymTtoeg4&sr)TqL{|C&*F0Q%4alG;yAOi2q3tSJ|*=d-l3 zW}*kW#O8AmQs&Mm&MmHD=*M=GZ!dJyZ61{bbyv$Rx3>t&fgPB?(P|?Q@%huBvw^e^ z|F+^YI4ws%*{vF(1xakT8nRj3%w|*dCyN<-K4VmG)XS2Wl)YLCa|kjNzh}-3YLU_~ zbSLap!K|c;I3f?~|0bt;r z*NWdudR?{TZ+EpJf|BU^M5JqHjDuYk$5d+))I7RfGMD6djeuk-A5ACz<@{o!LJVn4 zZRkI523c{#(H8XI{`Jy^RASI(x6U32=iZqj1jwK>D70}#2EtSRZR>_GKz!~O88x2BD34UOt>F~W> zG)l>02cbgCr{P0uU7U3-eR*b{n@?c=0Vo)MV|P+b;$|HlLuN`nfE z0jn0ad(zZCn0w5*1eYl9c17g6NjyWW5Ij7{zGC$IN5nQf<$Wv%}r^Yl33+*4khv z#Jo?cix<(L5H9{7@>*Y{6|+qs?p-%a)nMp9UOI@o910&wx{IQT8GUa{b293y44^bG?TqD~@xLP^OK z$ciCKyyU9I_!NMoF79&SYq%r;OLwYe>4Lh6FCsysgCv2h9&mW5vO6{gIbzW6B^dK*a{0)*2j zprc8gG+Fw1pkgo6j`Vc16&-1{6LG8xR{Z-gDB^TETzx-XLa-25IOx~pKQIsYjNCmT zR&!0z`g>@zr7}q3B)wvG_3iC6ZQ7-Ou)L!m0V2)-euA{gy+CiR*cmG`y6Z+STRg9& zIbCB8{LP}U_Xst>ABV@b(U`(mi_9?StqJ(2{vS4^8v88!(djgQ7=$c!Iy+a3?RH56 z2RhMbQfd7!!shb@*T`h4ftDhRk>NMN7C|WNG@(q0ifJZP!6H1jpfrI8q-3w?cy=d^ z%yH^!tya>qL}C8dYckVrsQyXa1@QfkVW7?_ zJ8#1|B*$2r0$xMt2TR4!Y)zA&#X{v9m-)MH|NUQmFdP_k*0e>Tx-suhC~>u=_sQ+l zx6PkDoG+l0-Xx1HWpE#3Q%nGj0K@xVwXFETy6ks*n2IG0;DT+<4jR(wKhq*a{dESm z?=$v7R`tRT>f{r?s;b-IRqg?u{=_T^tkvw<{BhdGJ?GZG)#riRT;3CW?=u4tNgM^#eMnnc&!%NC*%C@*TOEiTKF?ttngV_Rt4XVy!&25aL;!peWa=Z+D^9M zrSgysjL1X$;sR6>$_!>st1C|-a~cZE%ZnT;zT3nfO#JLi@&XVOYJyV22DH|LdZo;E zyIxWVudvl^7M;_0&Pv&-Hc5h`BH>+;tpwQt^lnLFDj|&HBnxyFB#KS55P1o0B15&0Y7s% zNeguSD5mSZ<)P>0_DH<;BY_E8VH)W&mcsZwSH(7Qbd2?a-&(8Xn4Hk+4fT1L#yIT? z->}q>`BO<2L>pi{NR*6{nUuQ+KITkGOG)CK!?x7k_chX$h&mtI`)g`bn>!62MsB}{ zTJJKgmkc ze+`_9Ap@nC8J75G-U#hNVJu%oqG}tv&&*RpEGEOS`P?yhn`j&MitvsB0Ne?>2bHX` z7AD6X`IWCpI;;GoQikt&gv%hrwyo4?5dNuGn=7lC7N+#wU-STF0f8K*Ajsg_+S@!Q z1>Nd&W{C;zTRF5XUJ&)7kxeKUjEk$tsy`)4P2Ut|3AjdEAG-+rt%(t24~kfu(F8^d z2HyUQ;N<|x+Gspk@_@lX_+Np~Jj8Dp<}1&h`>aJ3p8h5x`u@od1{LcZ{{lmD6@TXj zw3yuE7|86O^4m<^lpY@NQ_WMP>J=adwmclYbX5bkiZQvAzn4%S7gVZOk}}7Md%00C z`3LS>S$u#Yr*T-^38_D&O~0+#&6eETFDKgLkFoopyI!pl)Lpk6nX?eVRP-#p1n`O6 zLj}bQ*sLr$D=K1q&W!rWQ=@Fbaz{Mb4kDu#5I0#juW`5HxoY|6*2 zRmjZzIc4YxgEpt!w<3d%?@1r!_3@$SsnuPC4%tue-w2oS>D3XdQ=WyV={ybbRlgr&&@EP=2xLx3H`bgcj z_=>S@a^A$0x|)k1O+e$Kw}GT^;#w|9kkV@RCiwUm(L& zszVilea3jd2#zmGRe#s@9s++NzSx7+b7CE8?9ngT5ixWUodEfyP(mLGkGy4$dkufT zRN#AG8=g1#A7@u9T2d1vX1`D?F7Mgi@ovTQ5nT#q_qNlcOpAXTZC0ew-q+nOVO-DU z{$osR(4CsqA+~JqGw!!uztsiYmJL3^WYHHCk>8m$<{*Bj3Fvs=4A~9Pfto*SkiF=; zr%Y+A$k$n$k)N>Gzx>pRCzPWO4H7~yY6V3unUBtGwo{MYQsh;SQEtQ|9}^2>E4>ho z3o`x;*@MM6Zo*M!YI(bGoJn^qUgDYJu|v4%X4yt>w*kE_ft=jZL}kwYXSLA}V2aGn zN2uaKe?si@bG&ipai8+zXKVEZ4i8YSaFE*aL0_QE`FiQ=x9Z_YX4%q5e=DOZ2z!5B zueGXtORP9k(gk`jy=7J8pf2CY&aQc3i|=ZOI`48}E==Re_1Np0?_c3>7gjWl-2)Xn zzg0|5G4;&7>}2=DgoY6l!>UC4Z9~FD2%a!_lS{8iS}?Esi!^AdEgf~4m}=i`L_;n7 z_!)mT((o01A-oVkwX)PeZIqiYqf=J`gARu{FJ-Cj%8%^nu{WK81Iw-XR}19^)tiAO z`ERfkt*LXO#Aocoud+1z7N=KT*5jFfZFe-rgD6r4iHZBTCG(EJx4y2>J>0Q|r}-R# zWe1b+YJBA7U)71$^}caV-8~6Tk=0GMn?3jQ5r`^r0QqVyHYGFa>R9Q03mO_32XfZc z1QE1wKkaD-&VIJ!X$p|YY*bt3MaHB32}EVxX|O$`&5p!lgAJpAg$i2!rO z)w%Oxmnap|qQG|@CQb1uM}2Y<{ytZ{oUV3A2&yW*40cjJ26=g6K3RLYR6T)BGdu7` zG+#Jr(r6XK#=v-_0O8tr5~Fojer8A?LQcvSFSi zqD4J1fJ%nd?^NLYtG@g%-W~g+I5Er5)7Yq|%%@%JT~KMdxVHOMDi}g6m)Ud-;CbB# zQz@Ih1=^4=Zzmd;@mQD36{w}=x4LtR9TzB!!*HLJTbLIfIV|LCT-kQ8`;^`yAXNsd ztIEj&kHJ^<&&@@sZK{!KsakV^kLgYowu54&oOb!?VAX6N-KaG1#?UA#TrkY9E=*f^ z7eMbsvxzjr^MUKjp+VF&Sa&&;lC`24%}wyx)Mb&}{R>TL0Z9Jw~^7i^G#MJsc z&|MmMNDtesHO?RR1Ko)ZQI5%s`x;H=KYd1c7fq6X2)6|I5%1vp`=Q{UuvN>*sSIkL z{za7|c$U zut!~ENfuF~pK9dZOAxP>Dm?J7IAMbiNjviwl<5T^8D>#!3A4VTQ31g^_#j1PCgGvO z;#6RLRU%m3*S(p(Pzb)+UM;r0#d3m@mgfG`ZT$4Or$tf4o26C>&34tpkJQ7IH~4P^ z8l|-XuX>ugAc6d;tYSS}({63E$}swwL=JYoZ~*I4(gC!Lr%HnWQp+w}fx$PwWGt>y zVVH2$^ooUzCI;4u@VH!dk~9a~&308p6Q=xYGZ&rZ?apb`PSjTG?W;hDERF%X^et&V7>~qg9e&jaK2w% zPstuoYiiU862q-q-s6~iD7Xk31R=`eg-74|8`{XY?pclUV2xmW(^j|}JR}JGolxM?H0&s9) z-I1CkMVxL#u6ch!V&7AZy7l&g(N5@!iG^%hF?$_;XMTJ^me(s z<+NxU6Is0bkPxXG$(VO;tG)_mbEaT~eR{69*wSi2oP~DgPyQe5y=7NiQMN7|pnw7b z6z(1z65QS03GN9H2<{f#-3jgxJh*FuyIXK~56-*Or|;?X>2v?UJMJe&F-EOjd+A(r zKH|$Xd~4nP85WIm8KJh-FsRY<&K}E}h|K7`LfBU5j9++FYwDusw;<%6(bL2z$sf!z ze!`aOvXRmJr$=t5lHps%CY){@k-GJfa!Wo5xAoXDgxnK&v-Yl*f8j9GG2z*K8l8OK z*L06OKSyE`bz^arw=S>MNa9sFYv>D?b)MlKpPY{cilPKTi`W`gCz*}pQOO@poTQ5l zKJAjKV<&!35=ytPU#O?WKpxfw>RKf9L1~ZiyI!*!YcZ8|s+->wPt`2u8{dAkUXry- zvL6({6vbLPP=SgEZx070RL#Os(WA*pi8;7Q^JU8$1Gfq)amoU*Sc30J?GStPPE%RKh8lNe)7_YLzl(bKV%l<-iv*|ktVl{OgMLPs$dI4 z`G7rnp!YJ#N$;Y{KRej}mSMZ9A@W;(V?x+h23opz1!d;-BsQJT!kP|waU{LySwjjE z3!pd5Un#otS-YT)VP+H7+>?{FzA5}#l$)&DRITu?f`!36_9F4mDFH`W=`<{q;D!uT zGqS#OR(cCJ2uXqALdw%rcXpbJl1s0?S(NN6KG3(R%(Kdi?8%3ZmjF{$N^FQI-gfRB zs$r5fI2GiFbKHCqT|BE`_ItW^2b-GEa)OvOga5EG z*lq^Dm!R4^g!d(98#(r7vnCJ;dL|0he_m19eriNDO3iY8L?}?cK97k0rZr_Mpa@;c zFrM&A3~3R5IJWl}yTWzpO4;DplXXS;JKr&EF5;{ALa7JO;2eS{GOmb^c>=cB2*E!> zBF?Yp3`))EdfA>&;5Y8tzQIWCA1^g5S$7?HSN+)2t@VC+9+WPp{+MTj`_k!}h>2qD zWyfLhwy4B8T>9;E_Rm)$U~QQb&rQQgqaQQ#!Kp(iSKq%gct2P&Wc&OO=k4MIdl7ca z`|b5yl1=k~b#IsLsP~41Y=t0oWSM=6?#i$5iThiqU{?vvt=HPRLU*1}muen5g zoTfg5;H5mtxJr8ERKUzuNogwI2}P3roeC4*nNLrRfk#GA>`=q>ZBIeowagjUCI*sXz zWy&fIZmK1#tyl$j-&OX19@in@a zqhU~<A( zM<0V8virMG0C3$?PvvMV`o#Ji^@zhVo5trU9sUe4)r36Ww2@Uag-&v8rTDzu9NM(2 zb?x(?ES5iyu5$*?d~tz}WE5ldBkhKwLId)8H*+_d2ot_7o*n zX+|?^0PmxG(K2JdvL{iai_?)7m!yWG-R0};!`TW|FUkuMP1kAq@eNr6;s}Ahv|TZS zl2xJ;-tWIdof{5U!3uEh47kN#mMgl0VDx?~y?7Cnd=B3WzKeE#P7St1|36v)maQiP z0@y;eoz!u>3yns3EkpM55H3>)7`F@R2V4ecsyYk9=cKM5VvY=nOtD6xRVw_R!SfBK zlxg`14z&r@-RQ=NNK_nLM|5fR2_(jhkx%gb-57@-W}ROt$7v==M5g3RNh>JtP%HCI zD9A}|RaHud^;fYls|f*=xt9bkew7Y;$G1EUVXMac) z)Fyl=`I$;0-8#8a?L@$1Iqx?86tMD{qx6=DJ-CaigNox&~pvkHH0=66@Mkq{xeaZ|ZqEbIYZ!&fHmlt4Er-nj%;`9g&LOYdH>= z#_o`Gj5%!Eswdr-?f!Use$`80GtV~k8LxT4SJmj3prJ^VKraDvP<-Imup$79K?DG- zM@|tvPs}9;cB&_c4ZCx;<7D63bz+S=lHS}ZjJ1;lkXEYL{-h*t#d&7J9 zURCFZnVDn_mn@Ut5KkY0uEZLn2!F>~4D*W=(Jo+ln0tD=dm2{QWJkf=J)o#%lnlZ@ zf4<^^ge#r^Y^0!zb$Ak0{QnMLx`(RX3(t08mlT9#Q1t4J1n0j-w-gDR1Um{ zaHT>*+yf|;P!qI+i5%-ci-vnYgfhrUB{0h>@zSQk)rJ)$&;a4c3&T;VUlV*G;k;NN zQm=rBpyZj1D9Dn(N>F*Rnat1rH?@k4N*Rl!uX1?cKrj~-^F|6+SD5FUm!FpwdgzkU zP-JL7`;=pY$9i=0{f44C`E{ccUgXp_wDO>-D44=&>1gt!VBp7TMMrZLh;)=KWT1)Y zXmVki0%eB$@$khb#WWjR=UstqH$hW0Kb?BM&HXw*$J3yNqr87mo0MSo0+DQvH%MM@ z17nlh>$RjtsJc_cHkf`uu58(Mw{7~HY=e;SF&l~JJgKJEA5fJZ{_C(b;yE?(?oQY; ziw%m9OWZ1xI+WcNuU);RY{-vZ;4HT>Gxqh!+0NG3Vv^pUDaBD(0>RwOl%iBV)>%)b zMwHgoi@h8Rl|o0Mkb#B61oJxCM0k06#L%68EpEZY(-*J3)oqM{;A7ovfVUCG&LtG5 ziYWvbaBGc3f-e}3=3)I^JZXe(I^m(HlBG)FQD78F_TqWS$kkp^l)>Q<*==k_SA{}u zcYOSZN z{CE5i76J|nT&C*bscIiswJkE&FDcx<>Ar?l;q}+vmCsq^CTvYUyDarRbI+&j(9HGE zS=S}cw|Z+XFmf8tb#aAlypa@)iN$7ZM;Ak%Q*vIHwOhiMQ)hU_RTK2NG19c&g_91G=p2l+u_IdR2YEQCS zYM8jOTACq~gAx+4V5E%^{{#N8g56~+B*lxo%acpdHbh78wMi!W%ByAo3Z5~yxh+4e z4?C;ZYjILP#@a7EFv(6L>Um=Na+q?p<`TuTw<}59E<>70qVNi>96EOqO>nhK6dpDH z`jOwChZU~cRnmQ%$O`t^cG%Bs3d2o|Q$#Quc5a+=XLlPzO4754$B{3WKX2-5!|hY| zkua|*F|&O@WDHA@lhf-oms;VJq}<0FqQ#{zFDHx1g2&iaKu4bvS)mr<&2PA!Wx&9t z7M`M`$W?St>N9;46W8%%$v4VW+4q^>R+`c4HsBA`)t3x*K;R=hmYHr{U*pYe)UIS+i!G1@g^o?-5n zq`*#oFLyUfHddhLaU)SHkbk@N@|UNXxst3NZV0}FaECvQrmaF$JFy8HAN9?I!g+bm zJS3dqwBx0H@dW_61)LXAu``)M-9b&sE;j%KY`U821%c>eTfuwo82u2_p!52hIVjb) zVlz{ItL(k5dk;$R5qH@ObU-C95=^MI@_Kygn!pwgb&@M#+2nT(YM4rxd{{Qm`H_de zNxy`6&Atpn*oPATCp}x#$5oPew289ou6CBNOIh==3SFK~e8}er5oS5_9~U3kQhNZ2 zX8lNY7u(P}x2|O2AoSnY$gsC93m|?S8O9zA^<2J6# zTZdBjku_;m#ZnayHDn_`j+Tc^fg78Vfq@)pHNLZNZpEGYnrA-*Ti7T2hKIo1cG=~{ z%VI4I1!79Gi1{-e4{lH+s*NT43;FOzSNjOz3z$t3B_|178M(no`kh|Bv+uav_W3SpoNlbS^BQMj8s*w#Pq13s$YcNK&L-n#-T~Lc;0{9Sl!eW?bNkv5Ev0i!n z!zt^A6wI-&tdjDc=VUzrV>C}(7BRE-!tGVEaMFQY!~(_u;*^uBVJR4cn=5b14?z%& z8l$Il+(!b&di=ta4FE@LcYFnk8f@3NFKfOYKtM>(wVnVj--1$$DPh?td$fXMh2)^c(<%b z2VfCKKu5`Q;P@P{;4Apzu(!#?Z}&4vA#Lib&V#&n`2=M%5ll{F@u(#^p+ zdR=5ytdrom2*0@}jd?&h&8hbRx+ywzaY|9JoV+MIBj;ToH^b+M7LWAFooNMY zr>Bw@K$ml71}LSE>u?3YK_S3bkAhaq4l7zlw>MNs`}|fSl+$NsmK=OoIQ)+hpD_`~B~xJ0RQlyhW#85_G?So9wJnOF14;vr_2*=S2+ z2>YVj>$0Cyr@jjBbc9LM{}E`6V_7e@!aAMK1kXNRQ&T2?{0=9{r=DbIKIuAJ74nhX zKNLQOAh?d>mQXR9T(c$DFE9E7&JVlWS#r7o`i}+#pRv0qR3XCKN_J2WLp1p!9C);* zQ}Ha%zCCgU0eQKaZa`*3GUc@MCG0UIER3{Je*TWS48Wyzi-~i`lMZWkmTi+_pnosd zR<@s-#r(OhLqAVuft{9WSVt8!v-owjOy8(iV1JBSb*JfGK9y;T$W#PPdL(z zohNKc-JZ6E!z#!7S#uZ!4hB?GATjI@ze(~7@$ekSC=^w8t^45djf?J@rq;$e4X^Q- zPlpz);od}|Oobe#3>&K>#LrFSNiL~*UAMXGU=8Wd$OMnVFO{%y#lXc&kFc0P{6n#* zinW=9J3k;UQl+qQV=ygb@VKa)YJA1FBY4G|*>wsg*({pH#F5Bud%A)pzqGLn!iDy=O^SFHzk!KL*|{E%{~YmMHHS8#vs)q_0dUZn28!Cu?f!*m-LduNZesCB zztbooCc<0$$GAUrQ8(?>O=L4>S64YN5R%}6D;(lCQ)xCxPLYnM=BbU+CtKYd@EvzZ z#k+YgYXY33SFP~IJcM))AB2TAdEs-hH)U!aAl0}-DePQK`dzqKPxylYfUXi28YoHv zA@ww1w>S$y0F#I7*bkd6D1M zRFqesN(|mbE|Peo*O@P6Sj@>5C1^{RVRh#tV9=ux{(`+c0<3_hN-<6f{FVu;fg1&F z8MfPEr?6ws2&V={Nz}pyyS~%5`T=xESaMP8QA+;+o?(0)bvVW7kCHB*_%~!Oh)%t0 z1Y;4>2@`PB@q-5s%;W6nkj!PB_)k^2VTp8}kF9Q|ul<+?KF2$I9egvcM zD^tO-WFo@py88tnq}NCX6uGG+6e4e&uRxx+RM@V~0uw zLu$8X29D1ZYQ=axyG9LNs;UGpxw;u`NFH?)b`v=uO?t4W=vC4@H@DljcG-o1T5`nP zL?CF?T22EYye&7$1=!Mkr?}fjxnZG(jnbq*3g|db-fc=km{{K3G%nRKMY{f=dt&aH z2XZZ})8w)I%K{qq;{2SN7*(vEcz69z;K_AQbcKTpbGw)oXlY;#hpFkq0D}!g-MxP8 zQOb@PIT^1QrNP6^*3EZRUIWLobE^$Ya;0~<7Ke}gL9~>;mWB%XrMjDCGFctc}4qN+LM?-cAkxtaCr%be%Gh=*xQ>i@g3Efrbm&@8d z+uHEd*R?gQ#nz|VkH;+S)R`>J*Jil^qT}|z5X0!_?JT%|Rt1Gn`t?+wH*p;6jBA-x z=5!>zEmVcS(zt0&k%Ch_%b9~NoKuBTIE#cR`0-f62ewZytv-0svO<2}^3e9lAV0Ta zm93;6_wtXxSdC|tHx%8u#{HQEqjS5=voQWr-f(fft$t0>P{?ZWFtnuPtz=6jbOiVk zw@5e$SC^Yj!Xlu!kj~Tjug=PihJ+!`hle&VeI#GhOwIV+Z@~Dv?KgA2#iY|aby^r~ zxc-cpH`}*CCdRu%Tcfvf>x;<*Sx^t-#@8Cq%-B~>5xIvahg^S#>}>;8eMz~48iT>l zEF08*Sv27_bII&Q1e8~}@w!>~nF7K_U(=d`!knLl9oKA7VAI4Wu*!G_a}2`6=D(W@ z@PbPN4zNr2k`m%5$>I*!r1B@@WOx!16)Cmk47apsy22cfOei+0lHfv+&3*P5AMJlI zO`!Dzz>OyAX!=uBBn;+;bE`3wn%8j4l>ac7Den?5u=HEKRSp_iQZ+~rg!F27*;)a*D+CIl*HKrQQg1> zq~Jf|Jh8I;?o$sE)UCB|o<{M5M_wDYzM@-x{dmZ=ZSFt;oD zmD+L|sF>-rrb`BC_|@!l+w7=OY(stbN-~2B7`mOprz$ap%awxjmm>zO1J+Dhv$}@% z{2;lEAEye2#rDedJGIGnz;BL&z*O<@YI5iuteVcxDSJx2Gc=!XaOvzlim}Y-@6RmJ zThY|xSV9Wi;*MwX#Z=0^rZLGQJCS|fVAK7z9$PNe=<4_o*0;R=9 z(9u93DB(^Gke`C>t+sO07#fruCfei_4IUoeU~JlX`VkUzPJocaAzAe}Ak00AhP7Tyyaj^O*P+s7A% z&wb+TaKnmZ`xA&cnhqinnm=auTwU187YHF6qp_8U_+Iu0VF7dpxze$MgjadWvIhGf z@2A2)nYmGp@Aly|WZ)tinDdhF@t8n)niM>Z32loVAw>z{IwSh>e3n`gmOu|K-s9(? zLlXHxaS$6q3V99sqyDs5^~K-bJ+znvthd_Ya%zal_z?{4*Qp!<)VC$ihY}?*{`7$m z0G9suKT%j9u9a{9^|Su*MBrP09{k^b)+2!y)}Nm9Z~p!N|N7Jk26vH-{Abhud{8$r z6d70dGu9x?f4BLs@BY_=AcT`}|NgW7^@;Qd4dO>}WRS!6Z$Inbn&=|4`hSoASC{?O z7yqyG|9?I8G(ph+T+_e737E+-b;B}=iHRo|L1!}|A>gojG1bqypTTn~B~<@f(EnID zw^2eM)Omj%KNzJ(o%9S6N?_u z15K!tDk&)mVPRpZ=Obnf#m2@aq@>^qU?rs@{*PUQm>h!Xmp7~}aTjb)`<5paA<`VR z6#L7-ple{myhTC2HtmN{?Cq2@4}TRC;Z9;GbL#|c*<(dwSV`sy=G*~0%vMP^(7$h2 z9-xwpHLjEl(a`VbM&AmIP!o40$4_WzXmh7)c`{eu(859qlWsx!$Hq`CYcMr@L@eLZ z(hkggxzX|EC!7rS2ihu3G|7_V;gmeuQd{JiJ}h`W*LixgHu{dOG_KDn){#IJYgGH4DMpWFbR4D8BG%8gLH<5 zAP=Ha%#_REbtkE_s=BC7hUF8!)?>gkh`_cY!EdRAxfqYHieSN+b zlWGrs-&2}SD}>G>?3B(eb@kW2E_wpQ*b+a*RJvF=ffM3(FG-9YV)jmHRjIAs`M<1Tqkz-D+HKRVrDU=R#eo6~pTPfSie z{5;L^_HEL^T%{uNt-$hByBBZEq5c2tbwVUCC}54ICh40}RUfqBvjBz24)i-&N1^QB z4LtUO6jdM2(Yw}ZMApW(*+n&v=fC^EFS{KI+WLL@C0jX9LdDUs_Bh?H$VYUXn9p?_ z@TNFj&5}QyeG)~&VaR{io+7$=(jJb_ZecDP>@f_2e|xeJFHWyfk$c*nCfTNNBo3&< zv)!)27hD`sLiNfOmb=Ejg;Vfuu=^$7EF>~w|iL3l!#vgGJo!Ejt34hBo@n!-}Qi; z!qwR=d@udR0f1zg0?e;ZVhT$zTAYt_ar9k%fu&Ncm?gk=(5^G1%mx~As%L|8zAm1-?EGgF~|DC20gBdhFvDWkN#Jqslzza6Zd1Im=EDZ5_G=fZ+5j%Vmaz+Cqh~Cw-n>u4j!}%_`+2vFLppoD3 zaMm^;A95-w+o9Ecnvz&oQRfvV6MVU-5!GsXOk-_Vtv#Qg*9>-iPQP7$AelKfpXwx} zVj&$<*!Pf0T*&n9vbC76%9(uA?eOwA_3ogd#_Pe^-RJ^|?R>xJ=nH2*M0448^P0{^ z&e7$=iJ{6CH61N&j$qQG@M#BUMn$W|=fsZ>X+nafoOiD%yg}L;4jmbQOVAC{$;IQR zb;YaahtnmwiL-|@b;H@6VGv@+$HyqLRj=Pf8OysLnr}D4X;&b0%ON;YWh-qSr8md& zdIFi}%*U$@2ETHN_T3#i02tW6LWVVSaZ*x{WO_K(U>+8mcNRV7+5JZNyS;MG)m)(T zQi>4!g-VW3yZ*EEW?yuXZxQNr(aeW~dL5fQz@U3+)CfMmpL5l7lgamwJuxx?PXr1P z&j(plAnBup$kUZ`DXQEeI z$L*ci{ib6%OS5U;PJD`mB7;6gWF`XAgyNq>xu)otsbClGPIymCRDZQNaq3@KkYIS2 zNBFFr2jmNpumEL^lB|moQdIN?6V+_X%C`3fV%&R3LP}mQPrqvc*_Hw$XkABqGcCWe zl(RJuyy_dXPj)CM4wi$UA@z){*1u!_96z6jj}E97U$fFAvBMp(KBHO-QpLY|xAqN!E?)aNRPLpm*B19InX(W$L-}ZhuZkS5c(BDs37IH7{5QTk;m>N@h6H&tl zV1YBNTh}Fmn={-GzFAXiS6p0PJ*F=>V7_G{7~g_B5l@2-byzP(TG-k^Dq0|WDy?iS z$;6>w9hKu;(=l0?{K#hiLrOdsS7SV zH*6uriVVZUG~m!YW!2!zbs?7hG*xGblZe@9vs(Wve(6yLCKoC|KNy8*`FZ_!5D&l? zj>pHBvu>r;t&7(%P=E7gShoZnqFDK~Y8key6foWirT%MU#wmq_ET8Q;yC5MpHm9Ex z^5ZNJTI=z_cxH}g2-gW3?=w|YM!`?+wci7MS;a$*8h<#j|60pHIMsm=RXs^z3Ui|c zzaIkcxeY7sxYwXw`;bxj>0$G9yl{zXDyTFwAK8RhRflksuY!VT@=}Uf3d>PnPJS(D zfRE~zf!AH)rOemSKP6M=EpY%=wb%Y|%3464%4`DnK&)xRiz;H=oVN3fsJkPwNs!!7Meg&uLaJ0o7p!#Rb&ZLvPx6X1up!KT{ zR{q06^Mebe_6u!qDC9yFysGl&Mw1Jr>R?*fR5w;UFjZi+S>akre;ltUAPrOq*xYihqor5irv$StE82UVJ1S47l6M`gBZtz;4tQT5OEwZW7d3`QY1fJMBKW^R(yC*pO^_l1cz zPgyA9@dVl5EJ%+{P8P@}zKc7wY0Q+)V@;g+5x>P* zz0fa&ntkLh?eqV!b}ufbRUDAvE>XqWybOzgsZJk?-rP{I_~fCYyXlUJw#oTgzacHP z(`s-?Rr8lujh-H>>rb>FFrGF?w=VE)&-aJUAAWkAwn^@0xDX2;6SJ5OiU*6#DkjSGMNLIXLP8PDWqhP}!jG zuMdlAgOVP&;DO)aj)X6ofpkcRI?xjOu=3@zM2Be4XO@%YCVQ z@air7kcSn%2~q0;`~s|MO$M(Eojj-6?@Z>s*B|ZU;^Rdx=edu#MaS%tQIldj{SSKk?n>5TGzfN5U38bn+Z8$kSg8K;5XMk{n;Oy@~}g5%KM{jYNP(5;^thX zV8wR&p|XQ$SN0mhQ3}@3vXj#Vp(|PS?Ak4WRw7DIh-x=?6iOF0{z>u$ zPe}v7VvFpA(N|QJrV02^4VJ$Art`YlH=8bTqW?JPFCBqB|H~v%Q?cEa7==VQ zl8;QY@>;?baD>)p;PbgMbxDHADFJL?w{v?5k-5jR_v#bMKt`?Z$AS87d=%>0Zm(YO zIv%T~wh_8PU*49)wP|^LTJ$)@sE^qhVO0}DJ(|n!^gW@AI`L~4?VvF6aukDzB{u^c zG)y$3)+n#rx>pE6eB+tI_CA=l)SbxfjHD|T!c9YSX_l$rNc(du-};fJ-Hg&N6b9Z; zbu5;41(Bc*V%r3yZaA+W9#boqg>-T6|7~(W`$~CdsZm40BY!cJX0q#HHTt*;fN=ajTw2gk z{l_iFj=K4^etoyY@&fOHf?QwFbH$NcJ-VEg?`@12wx$Oss5III5c{$r+f#kOa zP%dhCH89}Kvb;Fty*x@TV2s{yD&d&Fbv&3JsLV3v*dkbROzu+4hegjVPCWO6*Q4-ngTu@Gb@>l3%?-YCgbw40v*qP|q3gQ%RL zcLbuW0z4wmdT!gAv+dFjjaVNyi68Ym&kK%Ncd52dp%8Kc9Rb0?#X*-w?htB$mq!P6 z?cbX%rBYlwx!!l%I0ccf{6@cWqxN^dcwE0==7gl~m5s)bw;TvVHZDcwhFMflB^*%7ie&s%_LgWZ}MFgi(%ay(kwd{O%$XKu*p12u(Ao=mO_8HZh%r$Bl-jXG$dxhXEp5up3LE~iH##-xkY9?wxN4(!b_&2Zxx%{guldi#g2#8 z4Sk`&439IL&OS5*F8w6yOXtjgo3LBso3w58Xr;sDLif=I1;$qQW++x zU=!Y3K%GLkg;f>Hi)aY}g9v#RSywIKnXV*wEJ~E$4x1lkw{SHKuT@I zkqc|eN*W+MN{){Hp`Tl{*e!gACv=MO3w@<5Re%HN_>;D)GpKhB9`;4U<%K8F2TUWw>MlKe7(OtKjU=qde!ca5I7k|gG zZLu_}?9rvS)4v-bT+||~k~at87PE{(=5;GD9n-z+x$YH#Ivg zkck?bn2s>~wDuZPA(cON0w?M{S#i#Jwfl@kTGJG;ixzrL*ILXBo}SHTPK!?dT{?k` zf&=)vl;aXbALe%@CRXLF&n&c@5c@F_YEEHLe$O*S8WV7*;;Wh6&3xqqLvuZ_4kB}| z816n!xsEhOBe2LJ53{&kd`RAcn-r0SDorM3PDgl8-lxx<1eu+t!5vTY!tM;9!2B?B zz|IEXP2i8wZ4Tg6ky4^OZ@Ys3VA31eq17Ztt;gfAFln^Sk%L~}xqglHJJsf%Oz zX{nIrt+=X2mrQoQi(zJX7h-6Rm(#(Qm@%B{)!R6P8f&k>bN8lT296Qs7T?#wJYX(R z*L6Mc#5&pX@3y6Q@7#&9ieOq0?0i-41Pw< zI(7*%;b|YGs+sygd^L0TZ`x_AgO_fy5o@7pNz!F5gcV$^At z6IZ%_YFOP*7Vju-MJpsgi4gzS#q&=(aG3%!S@S!t3?_lsn>MlLa?lWQkhj(lFY(Cf zssJ+V4ytA_ix-;jBM1d44)yQ@hfz%n4#tgHmdE=*5%wJz)$p%bM9zd3e3ie~LmYuT z&b^Y=h!eikI*p8!RF$l12E4oaif*rqLx%%c=MO`3@?mOqmj)Y${`wq1h{pfa+S>v< zSO-V@5_m#L)3)gz1heyWLPEM{o8f{73mNhbgF^m;8ew@Kl4vW*m*z{Q^+} zAYs*LgbKqfYbldo4I+W$lgJlSyV?4YPr7ZY4m^s{M#lL>2O^?m>~%>)IzWk4VzG87 zS}+Nn%`(b)VnV3d@z~g7ZiUa`GGeEmhKEVr*8u4<>&DL4rL;1Tw|@N9M8`UCH=YQ( zCQ!@bF%EE8Wn3|&`}A*FRBQ-X&(9ye{6Qc?5$o=b{eqYd5s~KM&>+-l+%o@B2nW&W zQ;omGe7~UW3(O#I$9FzCLf{xhJ8-ZkDbO=&4SM5T@M z4W%rB(V0|zOBz_Aplp9)X>he*#yCh^+h>qbP1=?q3hzlU(rc7Yx70IIHAwgx1eN4w z8~((LPZoUPD|4?n4IXJ{Wxtv&pC^e3hVIKXWk8F=LL(~C`J#OruSa8?lG=$@uJuRs zGwnd28#68MoRnV*FR$?RuoE$Gy=-xi3dZtCIm4wSTZw%=rQjTSI^`PO@UZIbJ>NvL zI96q}+t@~Q3Zo$Q9d@EN#*QcT<4lSAU@i1mNK9cn*weoC=H7C!6_M)w=yT@RtB65- zvczkr+85j8E>SpoD^jZxSY$>AfUY%0bB*)YIiKAO0n1}xQDF@tg>w@gA(qh4RaBU{ zq}hD5L)(67^&6DTQcy;)`;iIts}S`%oaxf_;u|$WtZW2}N$>0Tb%5CL&C$2_U)RqZ z7FbYvhA^1>0h5d$rBmM=ui1l7ms&ru{!ugTWl)Q4W#YYnDLPmm5A@q(?lK z#yKWoO^PPxdbTTL*go~$L{cL;BNP7F)vWh7$SMTT>CNAL`Q19@0quky^?79UbEF%^ z@JFDQ5>Yh$2G>dADFRxaJmzBsXc6kn%LqG0f~TyoJqY3I;^WW^*&rU5Eh)S8k(B2C zRrNJ8)NMSJG~&6MhY8?S_eUg{HVdC~)t=SqU|RZ@H^Ym=q%<|HAJ)@1R>m3=JOVf8 zzhkXsvNd z(RrV)p0gXVHtp!JJ0t@RWBdRj*A0hCe8gdyLJp*Y20o<{SU39WyIo^Nv&o%RthG8{ zAVn>{?e{#I|4vsDG39sr0V*wv|MJS{5>DgOl^euRGEc3~p$jKycIB1yM6NhebT*7M z7?}=Yf6^}y&B99Q2Ztt?cD0(2Tr{sDm%%gRyrSMxOIhJdRIc|PMqD=b@5ms45t>Ze z7O)FYJuf8s!862;#E@)I8%xn>>lKA995T6N-4eOuC8!u^8|W6|Rt*!6DffPzdZYZC z4K0RG##dejCg+-MY+ilr1y9j`6es%~zyx551J+);D_xTDU?|$TZk>ojTy-dIhsDZ?i zeum&dts4My>LA-MdVu%QUCzsc&g){I8uS9)4n7Fkdn+;HB`1`lWfI}IDZ8@cp#+yZ zl&ZwW~-^5ihl zH{Yxg$SPO~=0^6!TL!XU%ekWZ3Vgnh3=Ks3Rmo^ z!=X7Lv4sex+9sun!0^)PwpjKh38R!vnwJ^>g0sf^CIuQkFOAdYJ;tT!NNUsc(JN^m z+d@q;30I|Cd?tl{{&H41NfuUCTETZ&`0UnV$OIg(d2TXiXQmuALxaB}iY#{%!Up)i z1CGC?dTUi}K|mkQ{_gGaHu4lEmseoWtCiSy#|RzKLh&Le2fDxJgx;h)C8@c5Ro{+g zn!wt|QX7(IOD(yG`M0Dn5Y`Jxp%c!g>uP(SdwnMFj(YpFY4GUL)9OdkMIUY@=$@K; zn17p`9N!`uxMr(Tm!0XiF!VQgZlJhg|p@-W_+73Z$sBD?&?kD^l4TzL6rwNS!M3`$Z$Jk zea#W=wzZ=DGbKz~E!O2>DXQwtdFMP*P>-i1kQoz#ai3NeMVS;mTImLnUTP-$hDd0 z`H0?t`w$Q@6a*hI4{=*#O(OgsL-SwFL~(;eeoO@NpP(ht$m}i06&F4k*xn&zXxqNQ zBI7z*jUhz%A2^(UzFkh>pJ^65fi1KD%aVd zEa~salQiwJI$G1@{D;ReMf-nk8-IQIA2V2-)DZ#Y%SrJMR~h~mPVqTKv*0{o`>TQN?AGZT_tJ|} zoG$}<|NFxsAZTA9DGWl_PHpPiqWtz>11go)*YSjM*4cy!8l@T!LTmJHf6c+aE2VzG zRt9$S3x4gX^?$SgC|EdxCMBn_cJA zlkL>^2?L?i1FLLN>}MLw zK(pn$L?S-1L~-%K09Yy-wDa>1(&ge4k(0C)>KHNyvq8oLg#i3Np=VT#!e93Do+QLi z^uIvQ|I0k<6a;lAuQBnt6x5}c!P|dhXowj^3MhdciSDF7l zDZ%U@XhIwIX=23#ivO*ZKfT-s4T$>?BH7&0xb*@^zC{4nln77Cccu&-$`|y%4S1(F z*mf`GELMhIQgPs2?gigXowBYBmN=!FvXT_mk-xuLTX4hH+XF^PCErtIXFhy9oSujO zkG-#qin?9<1*97U5u_x9AqAvcK{}*k2x%z+m2O0ojvF!3lK{`jeyCl!ee$Uz4 z=Xv&8=kr~=bYvSuJA z=(wH2V>3Nu&Lx^YIy!2MhTx*|-xfy1D)f)fT3%`Er?$4l_zW2u{cwF6E6aHXNG*>| zcb9?3Ofz2IOmeb0BRwWkZ*nkcw+_Fkf1Z5k_>@d80`f)PtSutbmZ|+&uA8%N6Hj|a z_A52Qv_d)pcN2*=iMl>EHFgs-GtPkfl6U+07}NA-baJxN<=I{~U=3A;r0VSH_ZrTU zXn5^B01SEz;N@X}mBE>668>x?|9$lNI`Gk1eSu(7QdFD{cIZn;OiF(i)|BHs=g2hm zO=cy4@5;*M>t504BPk2*#iqk6XtYxIgEZe;hv3hp%4!8VL^Guj`uDby2LbqgGmZiv+h^`YYtg;=kIjN_kF*{|6(AORkw1e{4DF$?aj4b%(u93 z3n$jc94)(AxPL`n{`a#`8|3GHaKpSvSso4uJ5yF<;^hY1S2PDc=oL3Qsi@Yn${v9X2gKAu^h~)$-d(MSg=T`QJ^~ zE?T|56|9!fI4ESQ-v88QN|rx)FuPi#|NH5XDr2UU;OP{B=F-?Z4aD>B=p^EL%uGx| zxwb||G!5J*tYIUvH2>`)@-fEqA*1JGnsj;zRwx+PnG=&vMH0oQ2gP47<5NB&)x5UG zf+io%LYbg7F@fW@DSmo_%9~RSkQyti(qi^7Ry{2Ipp_Is3< z&_^G(ysEZG+qu0mXVNMOWtyz|bQnTl+4kw+L`~!AZ(mX72^XB`tGb}W+RAhR^OLNM z0oGuM)qBwd^iaE8tB^)D2=u=#U@5Y9<^(mO*rA&u&-B@3&EhZ|o>z<}Bp_PRn$M>= zG7+Y8ww+l&E5tMxAz6W%B$jhsTM`yZ$B>hEJKE@RX>nP(Qw|hn#1Y8!X6EKUJE^>^ zHJ!l#oE*&}eaAIBWhhjhOEg|4PqRqFi<%Nlyb`}_G9IsCr-J`2Fl#J?Xh8OJKJjIc zr=CPSuT2(o-0{s^E%)6(l>w1U>pPL4u0w{u>MPg*2q>Ez04lA)a;H80nUcCh7>p>o zhy&-)6=0z1=u>Wcb+IfDuuM%&hwp?Lz*3)tuZ?;Jy45eJIAjuc;ih2)!2zd|K`k zp1Ove8O^Mz@pbGjO}}O>xlHa1$T#x7GspATlnTBT0!DQ@;p6=mYC*Jpai&1o_I`(Z zg2|!XU4aUZN(4HG_uTO2>i4tNK#q$@r5TL^o#88#qn%waMcrv56^%+|?QUKL5D?!} zcqr;~czC!yvf-EdB4Fh9$!(aCvGEC{Zs!M{u>8*~g`-I+blarMIq2q4s-K?7KQyTS z+Vb(qk$uxzUcF(>UZild5EUfozjPsuURx;}|71Qu?|08{FH^1b<@1e!x$Sh?z1t~4 zymQ7tp5UgB2&ue?ndq1!L7$(M_^3!^U<8I_eNRE6Ng*mRCP5Y_2+yUVrck@G@RgPd zJZ6&I;4UwJoBZWn-_DNSZls(8#$Ye|?Cv)B=XG}KIPVhV@Va}Vy3&MMTY}R>k)S+x z39PL|$iM2H57K&``L>etJ4gei>YQS}Nlv8gR@2pC(EE*9NketSt&g3QUh+VCN-v2? z64BsxhDn4{>>0;t8b~hSF8xH|+j8YCn9sQKdo4+`Cj>i2B^w~7Bi~=qcE__9kqbCW z?+5}-%2zNFvHpA{sAn-1XyP2e(UvPGwlcj8-Emkv#~6E(FL}7yzct=i<3PU|n6KT? z!E#u8b1sC83$nh{s5o3f#p?er|IQvwJ( zufr1|QW{O)POAu^qPq=i`s&x?x^JVk%gkNoxJNM818eP;ysg?ZsGeY)mh^%+=9R>% zg;(u0|J7m#xb4^Z+KY0p95O&#A&3%&eG5rWBHX9l#f1}#4e)^@FO0fs0N>*IoXACv} zCB9S}+q!v$<(L`=5xZ`swSAs1AKk@t`P+s07d1XTyz|cK&sgzD_c>(LVbb?)`uX3#6J2;MawIt;&3GwvA3txq zXIRz2@)sF@on1}ms#Pj{yLEv+JzAZ9>4lSQ%+`?$GUbQmnCFI1sYP85GX!+La+~&Vht9-Yn=;M#Pe7adxXQW<#va0Iz{(kvW`w-FG9XIDyc}l&IFZC7>-tnFd z?I6}FqCHzOO2Mpjr0TqcBvX{yz9e84W>COYwP@AGJe^YVPpkR9ZL6oaq&~w)kICUio!kuM7c$Ng^XmY zFvU+!;a96aN?;gJ-gl?lP(~;4zpFjL+%R72kI(M0TzEBO^GH+L8~JY!l>o6wVz6$l zz~syaWSq~{B|E1?oqB3ax9Crwn`|bie#*!d z^(@;Y_QNV6`F)>f%o^XO{L}!?iOI?}#8U2nh$HnDSux=t+UqMCXtA z>{5hICyhrg$L2#JX4e81Olq#63|+2SOH!4^MZUjt_?!fyS(yNBbk`m)Fkmpiy;U(y zssYO7<$|htS%1pI@V{NI;He{$jD}rGi(E{_^Uzx&@?mq@m8)+y4sWuZjy&BMMvg8r z^)wztv~SVN9Xz75D)HS^Yhk7#R;rcwLRgp{%dV;a$&+KWf^r>cB%={zZXX_VDPfHa z48#j6Jz&?*R~e_#TKkA`AEts3LrFl8<1&7eJmqn=zyKJ$s5`AmHSP7q>Cq%9#ChQs z?opfV9R3>dugd`fMh31=3L~Gx@&LWPMhAZ1I#u$UOg}wK6kS*0k(V;9bZlB)*7LzQXak#8|l#qK+=y?GXVoqFn%QW{hRii)`Y#%_O2o z6!>shRijpb*50?=9kmh|eUd%zak6}|RTO*Aiz+-{yG*t_l0I|OMm_WOk53{$X|p#m z_Thg&kajqP=z+fX3bC>WlcMDrXjT?08wfpCEwe(b!gD$yo#djBh)0gyD>}sVOkOHNM$NP!?jU`oG53k9E;t&{OSA6C_KNLtL%h=s zHHOR{Fi$!Y_rvk}t1rRzl}lY2MDHjA{(dV&%2G2vAg{e&!xRf!G9TPf_asA3qZyP- z%FPUkgtLFscnE~^_p1Y&zqQH$<^E%^rw}2NQ;6Euj~43b*68t#T?19Ux+9nd91qDu z)kE8PJ6EfYLh0`osi#q-v%{3DRuXBY%^r8HSLPVvfHpj%a&+I85lEyX!9Lbwqy9Du z2+AQru|h6K(gWoD^h!xWDj6085WU)RA_#E1*Yy}j{cWQ`z_^m4{*D)-~WY%Qc_?aDc?4FrTy(*DyE+CUPUXbB=eW6ka<&UN}*_&@*vRDU;ikcfbP3`uX zsYXY_`An8u)h;KfOfnJH=Nsxq^VL;>+}470zpcj?1#1~h$yQwpvZn2i=RM zjiUgriP%!p-X2hDs3k{yrRPRo15Y3DdtOxsKK4z?na@zklx33Z1M^~+3NU+R{<&g# zizyS6ufH8etx&59YZO%Bgf>PZW#dl=eD00Z#ys6yUW}Y=kfc^ZKKn==ihNT}eZ6r^ zsyRUg=;Ce|BnsQv!yEjJmsyHmb8p^z9C3NwTydTswXm(A!f0yp4ReZTwMvZq%YpO7 zhFPmPgViuBH?>1twyH(s?D^rf&CKQv}$!tI}V=Im7h7Dr&}nIp5@m*_1_ zC_}p(ktSy7<<|0aM%Q1d6zMm>)+xip|FPM@ z#i%Vf`1&eSm#g1Mczt2jA7yD=jj_Lhr$mcD5aFO+j~dVus3Riad$MTo%w}4xk?bH^ ziUf2vk8gGNMNJzJCxVNM^Qw?z^3-x@t}o71V;&=mp3p==6Ho#mt)L;V34@-;(8+%%;vW|*vKX}= z_x;5D0Xkiguhg+wtl`)k6N=N5e7y>^X)7Kojccli&_K9G11PEiJuJm(KEMJTHf0VJ z2W#k-O>=H^G*%yF!@dc-IeIy&elYa62m3cfr~G?!BrQH52)vNJfY)ACnX49g1Z~u2 zG9@*Tc?4Lmejk@9dF(vpLuOM8dwA;xxM{~I@065Epm0=9_riS*E;7c}9En4!o1 zVdhcu=+*j24D@w^~kzf9+bpPUMTc#c)Sz8dqR&PiqNA)x(bsl~NkoWGyq z{(ChheT4iSE`1am&hU@&_U?<~zeN7~_x+#Og^y!^Z#`Z_eW~=1yAr&DTFq|%$=~{( z|9Sd<`9Lugw7q}))Vu%b^GwMbGxV7khRc8ZJTY)A_9_4W2maS%^ZyU&{}UHYtLg>z zK(a8yux1(^%L~+Q7zecV#}9hHiiB}GHa5ky^mKZrCptz2EBk$4>7)u1-t0V^GywOr zC_1$9y>?n!I`YD0Ig|5%8Rj6JHce^9n5tpZUl#0U1|RHkEVt685-BOk-rrp2^fanZ zzabJ1nda-b0u`w=zKg>#GXX2^KS4vPXwsicCXGOV7i@V{%Di(b?MCO6PXE z?X%l)@t1W0{}4G+uf_hNO{P1E-s)TVi1hZD-vHy`p=5WIsDV*D+!MU~u^2+kBh9qJ zk77!`GyZ*FIe$jdR!dKj{m}x5q2o*Qpmb&ikHoG(;p_#!EHz%kty&TS2%}1wnL-t3VTKJ-szc(7OsZ}V?!jcyZDy|o9Lh*)A6EsgNP1c z0tPC#OgKRi<>s8zd=voia(rFS#-f4G>T2`l%t&@JZX~N$jc&}x0CO0C2&7ZDimLLJ zn7Z|x7FHsBHm4K^<>cjqb*%^pv@<}d;LuP(bqdie%%3?O)oS_RZN&R-6<*7tU*jN| zmj8bCi_C|h>tZjXQw5w)LQnoU%VLHA359 zoLMs;+abuBjT<9hH(mfU?;aX%*Tsoft>X2>@7pLT$~UY43~`A5iWQ;pDF!5l#!FiM zK0ml$`eBPlyWXOtFOEab_0$z$l#MOwc$bsJOSH`aewQF+{VNDgf;Leqs&El(pI5H=6@Jb_T@+<*zml zt9_6vSfeDR?eF4Q!~K$ELL$~OvjLS$N>NX!w&mj|a(6xI$`95|=?I!rJwm9|gH%9H zOcYQKa69eJs-zeqLbOs+Mr+DKjc52`Jv}ZV?PE4$wo!nesjXNV>;XW7={~&0_gM$1 zJtL$jJ=&7zwEpPwMudG*f=ksA`t!@@_t$J&J*tm+s7xX3I$9w5`t@te{l$J(46e+F!EwHheHZXo#fIYIx{b*9?)NoX zw!oh}e8Xmc(|fB`7_Xk(_0)cI6jH@$AkGvmDO3FeORjA;DXcSl+M4yUu)IXV@& z%b=BlkJ~lvOg6q?)30R&8fFbBVXcx~_(IIhs(pUhxYIy=R&{5YN%!I_b_mMt zinC{NKO|9m%76xvtHn%LWcK-;RoPVL42tDCAhol&@9typi-eL?9qv!_rzvZa=Zz z&>iTB10};WUAq)`=ZJyf~50i z{YMz`0>|v7WDqeJ5|7URem`8r*?O{AJjwk~rlxAQf%5W#$y+x*3X3SzZ;xXc$V4Ip zH)kIMb9=RSzypuBh7_{-pl{B()O~xlc60}h92j@1t5Zl%BwzpK^b6iWFf{dj%2`g% zUxb^K;oLy%O3Lj3vtDS_!})w%V+i;D+7CM3Zh-D&2aOr#>Q31DiU&iK1N(sH(HH?p zpY`8dD60bKJUCB)=n?w|4;h?EsQ);U{GoY&(hA?qmlmdZBC~1tmBN0$V{vE4OqLP0rBIi#cLS;*=m2v4)Uk!UCP`~Rx}UC zXWl~S{a$cUN=}{yu{SFB;4r>SVX3ZJ?$4#%jXqfFz}l@TG4AN;=Urd{(3n$&kJaJI zK6|voJxoqU@$xjBv(?}YuF{Q>%-6C*Wd_g}BC{4QI| z|H@&0-&)kM=}De9Np19}pu>Q#F;o;O5XdAONWrkUVzAZ$k z+pF?)JskHhS}v&!hZW}CmlVQ$ROO%rnbL=?7YVnhYyo9K+&w$)g9BhHImqXS;Km#+{*|JvgJC z-ULt9Pxgw6U{4iooS9!F*S_Cf9IgVcyiV?iN0o0?dVZlIuss}o1K#0>Fbe$un7(OQ z1MRaDLf*uq;(;czn>8jvd7t_1Lmxk0C=c#{6RfW<#VDG%@nP!Znx0%wn?;(z6Q6Dzfq3w z=Dw21uLn-|#+8)W;8Q+(7g-@Uol@H6b`hoZHHFO|Vp_AaVJzw@!a@5&Os~>DzTX;G z&|ZYkHy`y8Y2A_wyqDItjRQZ@hg%9jRj_D`-3B0fjH|n7z_udHfOSy87}oE@-YVnM zKEO0-r(zbm$inkJ^5&c@ysCG$cPSXOs??zYn7+#Dyz4z8!k`DNBYB(mx%TD^Lc=hIDigB@ ze*?s67PZ~g9Q)hONaG7Pw^z+u`_W3l^&?Bfq{5cv#s)h(EfnVPW*;OgJk_Vh*jR*K z$oHC(DFe?J6mb>Y-5&Ohe^{s3D>dSAt3y-yTZ$;C_B%Gj;)yt4XG*?tfoD}XY&Q)!xJc2i+Y-B;t022 zd)k6Xu9P@zcAb8SR)r$q#(&R!nDGLhwDN5K)0X-HVY_-_!{y>uZZz`E3k1Wi>$8Qu zRH&rOu27908@t!u?ITIHVGkx~MscshkBWmPe@x=Vg3>E>>Z!A{3S6uEJE3TD91O!T z(xQsjuTVrkkdQ+iRw+jQlcoaQtod z1=^&L7Mf5@L_$J0aCjY7=Ih+iuC;ogY@~JF8_oe`(-jxy(rTPACozo*u6ZY2nQKb- zc5a3~O&YN~oWJ`aQsY+@zZsGjO~rk4PIV_0AGw#%+wWbi;9Yr$y$pW%D=Yt@sAoMU zE_#`p^yTS}>HMrqkG~l2M{y`9Q%z43rkK1JX5Kaw@a8DR<^CW;EX%?_uTN*sIzcwF zW`R~KlRdLY*@TPs<+on9!@zHn0R(t|ngpxpb>B~>r;k~bKVg$Dj-#5HqSbRW7s#V= zv;}kY4SoohN>BwHyn6AAxD4Z`Ryyp4zrlh>#K>LlF1%>fuzn%o1qc#0?q-8Z>#v;2 zQcs^V!U7+#Zsx`gA;GYfAAI#iWPm+VcA%>gBlSxOhfu*OmkSfSQ6kAyeLwl8KxW^KyPw?q&dXK?&= z4j$cesvGLWUgmn+c>a+2!tGZ%$N>sw8n>lU{IG3`Cwf(!Am}wVIngYE%q57n9^}70Qf^Hf{F2SX}Z777iYn<>>;4m7!N( zO7wA~pa00A9fu3D3CLb+ht3lE!6VQl({A{mknrq8kdO7q>KW(yU7QMlWH?$o>h3>dAupQOGoj--?pRwFJOY z;g}ZzIQtm4U{u3cGZhYm4qjX&5boBc#QskdGqIOs> zu7W`go71lxr&b7^%E{H88m5&poju3!RRT3+v@)r1al)+cO^X7GJ&&EjPK)7Sf(Y&B zuZShdK?CT%+bZn!=e~i68JD`hMV%fs*xr6@54#Z&m$AYuPs5$YgPGr z303_g3w!rt9iPbI-nUr!Hs844W;N2jTN_ZfWu4EDNYRo^fwnETa3LK?G5*>8?jpC& zYTh&8LZkNCqnM;Di7o7!;+|PvuM+Le(GFT^O^&C(n0&R@gG1FXr7If|^oXZ7XDVoA zy)9Y2&O$Vkj=tts!qc+1W{<^5^&@x25g*Qq&ThW8cI@V1HqC_0L)F6z4A;X`&D5i( znjy`UEDyGkutM)^1j@4lf@w5p^FF3;c9LsP0SmXr-MCZI>wXJjv{p6S6!v21cde2% z-KwWYxeD--e6%06gfNya&excz6+Vo-Ps3d+&hnT#e5PWb#l?#80RSeQ~WTx1nJpx4HVAULg+kWu^v@P zC!Ca$V}Ggiolm^H4sJ`Rqb?c8Hrkz$mD4)SZ?Tv*e*|e)aB3~g5aU;=KCR^Bo;01X zb;81q(=vWJ8;tU6IoAlU8l%ar`sE~AHJ?5h`=gF2iTr64>F9zg`}#Z5Yon*fM@phw z3Y+(12qXiE?3uCJiIFABu~ejJ1aVN;A1b%dOJSkv!ZDahu=Ode4^x;6y$hGN?j!15 zp6sj<7~VfX=>Xyt)Vjt~ym6jvH!?qc;R|94;?Z;hzjOht@@sM5%H} z^pfdW&Pq;qW@;e{&_}nrd^XxICsAxUl`1?`aK7!#L|ReApxbSI`^Ac%~o4lotlgoH*3HrpwJ zXDgp}1!45EZ;T*(oYD!)xv#`qOEZf4o`UeSvFxo23G91U^mNWLQig(D-zoH`6QSyT zl^17w>~^~QPX>jqgQcJ-)$GlS_r}Q`dHoELNlwON+hE4cKF1}GZf2?BZrkS^+>^o{ zo8?mP;v~lp+wLoM0TQZEQ!n}9U3iMrX3;?62iT(kQLf&s(zdackLoq zS-*9k_!ny4I|UT$_w%R4==LgP8`m69Jx{03#qvIjBHHrK+BL+}s7xq)QF1pZGc7YB zH|_flLsQ;-BhEj7!bb0CillRbpg#Et7^K%fk3EH9_(T-Def8dH1No#q%%k zN0dK_6W~lVXzIJPtK;pgr}P&WzEgQv$MnwUKQ(rtJyL2B{PUOSqxduFdI#gW%vtHA zd{$)#=xnW?qC5#kZ;ZOboaOI#=JKUzN?o)1aWV>ld?>$ucw=&#5Rq+C)ZQo_YIDitmw|zTQ4So}r4!wxhwUFGxa_CT;^pC+5k% z3bN*!NA|0Z@hVrlCz>%QA6$RDD93p_@mrWxWi@eq=K$6XA@_|%(ZQ#y2%fOYSCH=n&<8p1E9!oNxjWJE3ZQ$8OrV@e_AJKzV=XjA zdRzprc3;jQFEg@*UiGTjYL%IGLzI)?e4ULIBf|6H2Bk!&@42N1mKe-ul<@&#)wzS8 zSohu$>t>^JSPMW`tE-Nr&n&_lTb zHb#olrR_&8U3g>57*Zkag2!c|`}N60A@!G29>dj8!}JQv2?wlti=v61QcUl!x5&)R z8DD*g?+H~GxeFg!U9v%HJ(n%q$&rpy1WdpGY==!Zk$DA~C+zq5utEmVyZYbCJeEEKPf_BUaNdp}y_TQIr zib20kS6Jn_5n!?+(*hLGDov4O5WCt{9Ik&Sz$D$jK=7$RU(^&DJGBg^)beW~#>C`u z$4glrLZZiBe1{E`dN<{>0YkTVq8CHa5t0*f>-Wc*vQE&5T8cSaj}9rByYhKs>9QS% zv!|U#H8gCB7sbSk8AHQE(2-DZ{?dH%`V48Df|7dWRgY|HEAl-f9qz3Kqi}p*mT2*Wjc}?uNsI!fqQrZH}R*!mo zl|*hNzJA#fFzyWg(EHq1dkSbJN8?p@?#CdZJ6Jj;ihqhkBNI)5N!oEWW0whAd~f&} z=MiAqu{7T748+My-f*z|K+Se)MdaaLx`**r%GtgBgLlG5Q~Vb10suIyKKao9Ya)!O zJm+yWRjAe*wVgELM`n%`dY!S79lyMU3b+%dhivF47>FL(@%z9V1Gpo3d!uSkHknY+ zHd{048z6j!?J0VqI#rfCS+mXjFYv?A4tNcJBl{!4-p)I>@BLc1{fzbTA$e53H8)Uj zJA&7joPq-5`%5w&>-DVJA=V|q&ue%W_vBcrzp0;b{S2{9clQU9<6icyF*+|A)?E;8 zsuql|RmY-NsaEmj*_<-bt3Nq+_xtD@!3IduFbg_D=wM zmiRqXr!Lz;EX@2?6uEEvSBxR1SsH_Uf}v@NGxTd12}DPtHBp7Z{OMV3Ew=4v@uAz3 zn^GXEp(M3dKH9roREN&>x&Xk}(zhi+&+xIaxn)rlbSsF2^;8-Cmlpj^*`~ciub2jL zb~PE$qT-jElVrQi=y%R=S4mzDw&CPE{Rz1lW&Mm%9$w_N7k)yd(qz_Sz+xw z&m8=rI|pM7K>2)wOB=Tvx+6HsRiVzjHpvPZW}x)@&-y+>=rg2lr^S`gnZ*Lr+REhS zrsLsMQS7VmozlLR#zT7d#?8CKW~NeRLBvD-z2^}*!RRh%XJGiMYHuPSm|QRIRu@>()LQ{WQ@OMXF%H*cm#xwt-05f!5OeqOvEnAQFu`pGu)O5n>j#-?i9#I7ryK|0UL3%vAeD=CqNPaBI`hR$gPkX%>Xf z%xRc=Osj?FAK&SAZ{fZP8;9vghD=P(HPXspsJ0pM?u%v-;U!U%E_^FTfiR(;5YMK$ zI>%mki(mf}wee5D9sw#Y*G41L$hZ~R#4pe5zrW~yy`aSFD{kwDilg?DP%lgLz`d&32O&6QY6s3h$Vc6t0Ew-r^*b~gGU;unS0hjr34WsGxFDGE9qgPD&i1@m3yU^qj|bY?J{Dhp<`AWSi8b? zU`U2?jcD8odWVJCesiTr(Fwuq)@Ppskklg$6n*`ANcy{Dz5K-)RiD1`cyy_J6k4uy zX?|2xS8!R!>C!{zm6s(g%!P_M@?#$GQK6lB6%Nr%0b?31z6Tm4wKPe*cY0lvo;$78 z9iTS%qG{k(os8$OT+!EIapLwm_nWwY7OS1!@J5`bt_GLgf&*c-pM_i)A#b!@ZsekA zF{4X)e>$h@s724L$)hzEtU#?MMY_r8#r#pUE!d4&6i8ln4xE6Ei| z_U54X9`vP6qZNPW-RXSetA#(mykVZ?I18K2dCdWDOdp`2?EIv3144zzYEHL__d@TH z;MW<7odIG%CJHTHx~cIxA^K5=;?dfm6NFb8nt}btq6ayD<5{8iePpPj4&MPvTD4i?I){6mFTeEy>bRAevWYmb>yEL`^&xY<}6MIrU(|@ zO6)u)vrjsJt!!{Rm8~kA%h;0BGX+d!p@IcTj5$8z;A-F3CBX3$Zl&XiMlN-RVj-PP z6C)NA`HF3P@_ZwQqkeAi+iAfgTZ36Wmrl9-Qy?|t*34dg0c^y~EKYfY>hcevUaBm$ z5Ru>m^9FpW!iC57G1{)VU$~b-z0=pF3N20PjaS1Zh@U34B;&=5n9BAJJ$@mqf1XI@ zr?PzXIGOqAqQtl}7s1&~*yNe>#T%y;WF&i|)iFvb#NDTHHX)yjS&XcGNj0!7`W})L z&w8UsIrM_S;m5r(IxW`h2982RNV%;vNcKE%s#{=p;2VGO*en#SQB=7`%i{jKGEO4A z)1zlq@x8zXpyEef)|YImmDyZi4g{aJV-bm~-PuX7T=l%#Xf$IKji`I@ zlAsfO?;cY;IxOR7wt{Al8sm2PmW;D=b-C4oB6!8>G#(cE6=u*gMh!ayHvM|7Kq5$* zk*(P8tYmI!rw})co&jaT{q#R21;{V&?h&JOaUdqz_%XpTHBg*Ftoi;B#CL1W3s zG;tf-;|1}3w;vj1e+6pGT?Pq5Ri2`YCwzXDE(Ia+2IFRP#8bNw5xG;Znh|~wwUK!X z26{A?c-?wRkzjAvnRF9Qc)zIf7q4x2>#F-yG*$ZR*-s(U4qHmRUf}nMN;Sa#fbzUO z<$Q&b-K|=(d7F}y6hul;1F}}r7GGfCW*1Ck?FWVO_gM`j52W)R|dD&ZzU#N9v)*VmaK_=TCitB5}We@vsvCl&R*#HPFF3 zY=t!_Xpyk$)JtdLei{pW$2b%CmSm(I@Q?+QL55NsnVKc|o)4+J!3ZSF=j z7Q`kg1R)(zvl{o_wZWSa=44Xn!~|6PkZDsM&$9F(*8CJvG>!Wr;=G@dAnK$K6zTb> z0g(Rr90M7MA2R|}9lx1imrs(EEBEHhDZ9Y zy|AVFYSrg)%;~vlsE#7X$|$$p!>$*W{3pKnCRR+Ei7!ntDS9WY$*ktH7DPTO%S&cD zy2NasON;BXY2&RJ=SpvMc_sNNE-^~LiwTu-be|iLNWbAIU#&vzox$FMfZ5|Ch76&9 z?cz{?wHRcGPxko^PNjVzM}3(+R;!GFI+0eqn&tN$G&puO1=NTceUWl}LK9TQsi>OW{a zP&e*JiCHl54Y{;Ek=}VoUQ6ZKYEsdR0a|*=2W(pFm9$qIfKT1g=pZJPRzR{_YZm?U z!U9TGv2{Xp1r#YQRE#pDu6CURe3dcR)^7*cL0wES_gvQXTb|9Ay*9u;;`1ZXcW+7W z%b~{RP+=iS6QeWM>JZD#o~cEjCS|O9kLAbk@;`9W-8l+pL;q-k9lFl7W~x-Bv~t+; zBgd-!wRVI_rOHV(`9GU0O$y-ult)0vbj*z{&Y}EJ$ac6MWBTJ zr)BteKZZn&Uy6xa!dbSOuOeF-(=a7FMn=#Kp>JRcjjgbj=~gcYUZIIPKAlARV`=%f zmRanO5M2)?;KMv=nR&Dj-e{z}Kc)_%lq%y;_6KrucBOQk&GYV{d7LG{e-E=*(Z6FGw7Q=SP&aF7EN z8t8S>loF$kbWgjwrsMBPTYKku+` zu@UqPa4p%-+rQ!<{IHoXk=#Jd5kW$KI%4`IC^VvaQj3}LO`HjO)uOe8Nks4ztE5z+ zbtL31TX#4lefaq%i%Ls`z^sefUlzK%GvIS9MXkrkH?nD(>PuUo&wm>KkHbejijK^` zVEH|f^76$$jsZ9fcC7d*zDqjQJwj6dczd9rUqj9xef&mhF)e=^vc@jk$Im?8k;zQx zc2L^(Qa@aa88x7-t<5X2D8EVrmAGxb7%A+Nq)BBAmrPuAE^!Gzr&*QJdy^35l{W#> z7wa3K2UuB;NZ&Nco!3x5TpLUlQDg~8YL{dddz(+(^aRLeF_Kz<(bE)6^(b@=0Xm;( zCUu8`fYw$($(r6aUKbWVvl&+dnE#Rv-gs+{YPJcmjuR3PJODvJ?8GYH&J+g9&Z<2J$|h6W zRZa{#W}kGau$~T_M`Ij!ezTR{Sf%kLf1@Ra(B$yD2`+_tUe_loB`WR8+fObfz*VG( z@v;2Q+^Ui37xL-(4;5>PL=om#Jjy8MfY6-7xF~bFi*v}AI(Ny%G#1m&XlAC6{0XZ} zl{T|Vq+qj%V(1jB>C2BF4PvD@NgCO1%9QoHp--Dk`h4=4`H?v|IZXzZD{7RdhJXM_ zd;=3T8(nc=z9K@?c`+53`kF(UQA*k}P8d^b`us*}GT_!K=sQn#i{ z$)h0u1mAnIaSO56yPL*9Z0K7EKL=z)Mz^2B@??qeCLf%~con+|CQ%q_|JVc_?lPMB zjwEyaMfSg##eBI@qBICLD92%ff^8b(j`UZ1rt#k?bNoJu9p#T-2QI)FLx5CA*6QZ8(ez< z)0-jYgLHV|Jt16K_ODHbP>#Bsh3gZ{~3X-}|&a_gp!e_ z4he=+X!PN|9MkrTAbu~6igzwy(#u|~W?c{2NZ+l+u+YDSSx3vtjOzx_`y>l)jUoac82Z3l3kZ)kg)-fn0V9N^1*dY6@udiEl z+sx$w?oLkSow7y!>82HuWVjwKg%I7jZna@*Fr~1LfYUAud*gAM2{0GxpL{c*5_HU$ zt3iASg6(48{LJcBRT7TQw;s?MY{Md9lh$A+c1#j-i9FHTNVWHJ!MQYwldJJLx&iT& zu)`uy-fSS=8wA7%bs`w4*$n(-W0zv{^)5e0wfnzvNev{67UeI4y<9amG?CsXoB^K< z6Sp^yZoq-#OO3M(8&cy;ZzJB$>&Z3%G(T@*zi<7hr5_sEKkN~czAa=Yl!$6PMZ+vG86YC;G$3Q_d zvPxJeF;E;hR{&mkhMu0*QhDhQB{1e#r@aVcx6_{P4J`UJHT&+%p~T2M9c^YaDT!Ps zJc=$uh)$)=!fFNkoiD#vG97q%0nH-nRuE6A5dH2>YCP|nx5VG6C0hXii5eSb;AWe6 zNDb7z_IdGS%)K$JSUsNFiE3L!?lyffX##0=6sde-!KK-YM2EvQ(dEUC9gXH8>0Zfp2XPFPl}|2T z^KaS`gqcZrtRsSS9lJJ|R_F)sRnVD7>(b2zu0%{P_9{GK%E8NZ&P;{;Lp^WG9RFc z8TK0JUKQd@&>(l{0z6KPBlsj(!_M+w_4r3ZA7C~{&)h9$zbkY>{*8)4i~Z|0M1GNL zUZBiUH!2WU>(a7uu4Az;i8(~^dZm%f+q=?)*IUD6N^g*^6PZ5JKYwb(kAWP5VDyE0 z{`pf0Dc%ViVc-|1?7^{qkpOhXjWp<^X!Pi4F5lwT)<7V_lXE%9Z(w3o*;L+GB?OcWUyTxSMFt;vAno*{@7Tj1=_HTnF1%w~2BcTTrSx!9HSgcXO=suPXikb8t~ zEtm(SmJc571`CZZ=2Jy~_X_K&DO+3a@u7{pY}|}o+tn?2NWm|(SxW73U(7o{ia|*h zq-@@gwk5b+LRkUP;A*yy8!b@Yb zAo5N9hd{=pI)+v0zWMI@3YU#y~#?L z56Y1^8CrS}C+^$7rYV)6v$!UFqYa`m>y)&$mz&srm5;P6uH5hNdi0v~lR47t9ARzf zO?f~t1G-%>w&cce$Pg83KBy$%GH?*V|6cXDt(EZZ-VBGS+Gzogs(F6PY@5OIY+wkX z$8mM(C+V_@=DT}LFOYNgbu#y3N~#rx3|mEk~$_|q#v z4m+P)m!KHdj5}|+5-SsvY)1$M!@ko(gH=^R1=I~b?Y(_ZxeJGC4hG zoyChqv5^+sxBBUDzPnTOPE_7rUx==R?oX^Xje29>YeQ#Vc{)%g6ZK?dV@gp3ZRcUz zW36?758XMJH@2NM(3%0z1hoSZ;GVKzWJ`HE<5tJ z>2kbc*Xps7{#o!NFxM<1JK1G=f=y=n6om6&a;j3VV(da@&GEJTVG!Myu4lWfXY+1F zO#iFAvwn!`d-pv#ba#VDNrQys020#O-Jx`MgMffa3@9Oz(%mt1BP}5eARwJX$Ju;N ze9yh-+`r)R(=coA*=y~Iwbt{z->>HkPG{^1<2##A>$KiZ%-S{W%B9521fasDU?-9n zEv+=yjuXoukPEi2434Tsq7$F(J~@zPjWZZ?qZodFyZd{4FJ zE5zo#idHeug|9RCP4rvwgQa?TuP1_nb$29&Vn&$0l)~QoTa@slu;2CA6HNI`{o)9H zq#Zu^`Q&A*xgGSsXf1Ofrw6?*HfLsEY&N1l*jC4JT5WIYLK~*4G-SM77UE1wwThHe zUwLc~Uc6h{1k5!*sJD$;JRQ7nTD|vnb>5>~R&q!87pcEpX=`pf*`nHAPHskrA_KzY z(#4*8{U6$Fe!$cMcs-zABt7k&=Zyt5-oFju%hBruhe1`#;iZY(QieOt9$T0k)~+tn zetu_TLQ2<|o0|Y8LWzfoKShmEE+H9MR}EK~w9fF;bdp`2rDZSbg|xkkSjRA+!e^zJ z&|H?(Db-CCJ#3Pp8KAS7$q9KVsZnG=W$r-p4Q8;_*aQ^Gipsr#iVC=N5*z?rLGw0Z zeuuw^gi);X$$HQ?UV>v?x4YC>`3LYY{bsy+Fb9R4{u$zP+TvKKwM*(XOe+MUGU=28 zPQ|wZ`k1_dl5113M^g>uOV7@1xK9__5z@nB>aD;O>ed3K^pZn;o~l3YdEfr~0rhz2JUwMBiboZua*y{I%g{ zX9cvR0Oas9U>#HaMD^Ky==VXMC}PR+*UXCfkrMex=phNCL8@Rg&$R(om8U&;l7DsJ z07pnAX%*qE!YJ==$_8CmnvbTzzZbn~fHJ1=3cYGg***}({6B)X~iZxta0`?Q+o+m|S%IbkZUp;rAGENrQHL6&BPmUAr6Az4X9o zSf`HMcz0bn-tx@suFdakhKq<)fWWTd$1aH(7F&bxKTZ6J;G30TQC%~@n1M9l+v z=6ykOZyg@e<4-q$7lDFFkL(0;V|y-i3KXF%4S?;EUNT`LwR#;fbGIFb@6krx;vCX( z4hn{sF&4sB-c7beeZl&PMi)%~-0Szb}uQBkK4o*hsanO@uUoogm2(WLsPDMDF9tE9ZvKrqz>yCUs8k0jyldN=Itm!H8Rn-9|4=i(uVq#Y9fuOu7vL+ zvcRvCVsjGnP#jqkAk&s5I09@a3MQO_+9arwKiw2maK8Z{CdR9F1JQ4z{*n>f;|ev-|lAgIgbFP zm~^+X`Z_?Ep{LhIUP z=t54bVfQE#&ED^phJc*cfzFJG@dP05-Li)lby79abzE;z!P{pHM)p5QeT(<#Ih*40 z&4C8spsf1caf!UKJVaw91@>#TZm`xO0^yBqN`nW=vJf`Gdh(Xu=&oTpkhneD! zf5y`F*p!3s;d<~-k#zH9X8w~-g@m;#?FqgFSG;{EAGhyYk2;m6l)g9%C96@l8cf>` z-++(MRC#3NbIel#_ymgN4UMR03Mv*^Q+u67|3t+yn2LPVBDvHAx9fBQ9uilVl#|t; zOT+yOngWyv_I(+%U^(i`kZgZOI;JCrVYd1%2}F%)diYSI43LPeuomk>(0Hm!gXG_N zt^V@)`@6JpV4rx`92`pKFou>BfrgXT;=<&SPKlWGAV|UVXC5_UljmFkmD;s@n4d z< zjVYfJEGTC0Lk$ANgudAqdm9PB11v8{=CCTyePw5%=G7**-kP;?Dnr%!?kfyMjEHU8GIR^KMvETM^>tBBYBv%v&_$nQHmxFGYNShM z(3;bHna+}8+KUom!2%aH&PiRzYZWS?Az+o33MWXZ15#Y1qhFV7U;)URrIj6;eO29E z{DqcLai7?IxWd9o=H#jrz~cJ0U2$Dsy7G`BobGhHPBC>^|ISxrwWi3TW!qfQNHgc2 z5cLN*_1nGTaB~fTNyNZt!%hp`3x+>2a747YxPCZpje1X@ukXl&ntgyi8b33NpRx)h zbHgbf_7`f{vD*6LN!gu>fZ}(od+7S$wV<9aFdvjSVq;Nk$j1eAU2Mop1T@yg zSSeJ)jx#l^J+)}_{x@|yyM`MkD7vA(cqhyOW&3{I?N^^PyDX$pFR{x6VT9qUUiwAh zyv#|ifq_0qsl6+B!jCbu{t5<hCuhfqLg4A+36)GmFtC8A1@0i^2@3(MQ_VfbA z6r!r1J@tKONNM`MNk4~iJk0*_w}2H16qczXCPnYAHf+YvHDD=@MqXd zRcA^q10I39Id;A2q|Gyhok6K^!I#DzL~17(XM~;=w8K}R<6^8(gkn?R=WOREy#o0{>)-iO7lRl+u@YI%J#x=zg32nHV> zXj#7bL*N7S>R91#x|Qh)f8p!DFt?>j!_@CrS{tAX+PD441C&T=lu`>Akw)f@_FagA zF_C2`AG=2_n4;Sbz;<0%YW#QSJ9GGwbg9Z7{o#j9oJPh1Is-~Yotou_0%5XDGw`H{fEe3R)o$bmtx~_FQfvrt;0l>b|e=fqgdHbrm+x``IQ+ z^)d%kYCs5!Qh~piZ;I2!Da4>3D8*wHkN9GFxGwN?M~59bMG6r~m)zHqUes$>+wWj@ z-qXv=*(~rR%3bPIP&n&iqplQQj%c_>g*h^$#bSc!!t2{p#(9Xy+V`aMuK6=nl3rDY-frtn#SYsI-C z*~AGp2)*ESjo{;|Mk-}!;-!}rA)YFPJ%m}(B3m~lK@|rj^rHYLGLWQ?U+|rVBX?$8 ziN8Iwr}7%J_=vQ}cGiy0WpfkU-7b2v^nSQr@e=s4rraW~ zYU$^KdDu=ColYip}H2U0*5DkFLh8?8+MOldBSv5jiy%P=C~ty#M%+N+pQ!pG?IN_RyR@=$ke- zj~`zMZOD0^RUi_hdvAgglY~(|Pz`*sT$dkl&O~3e=DhmmMc7tmLE38;R>EeB;U+d} z7|6dx)rnT|K--h!4;gxWhtBC*302C{1sLuROHVeLFD_y!D22CSL+J36y!=!udt~ko z9=Cq%+?{S@9}p|~ewYM-5aQ+yo?-+TMUnV+q`Fzs)cNET5L?j*`1Y4s=7y(-w2g*H z{P8eov#zGBGp`CGQ$w+(dFh)>+4a}k;s$fY@EGZO>x;{-4*Wfo-SoH%i(A1S3zG^` z;Idq&@l-Om@uov}#c!KNXdVq)%tEo$tS#{1%gnng2@8ZoMD7$x-#5Jo4{I@%Ev_)~ zy}3#c8q8H>V;AE9EmxPnx8RW7^Z?f6b7j#BD41|-d22*G&8>Xw=wUlT(uW4AwysIg zkS8E+K{E&&`-%tLQ$HJyhz5GzlE~E6Mj*+;Fs(Hb2#(U!AS5{6+r zaMBJflLT?bu38S$jXb)RgSwSGdRS~{gNDK%Iq^sHoTSWbCo;+*utnGdv^~ zIP?dbI5IZSr$B9W7Kc1V_e)!(qO6w8Z zMN`F!F=G`SM+^S<{+;U&t4!D{^};5i?PE(U9CqgKFr>`BDY(^icLyOcg_eNBrtmtg z@R#+j9)QdKie439l{j|+d~8A7>gxVPf!yDOC*fY=#%8Q9>D3i;)gbxRl0La8llPI* zSaWKDh9cwwvSccqW|#>Ty!g!^-Ca_sKu2BfOMZ-<*5k%_9!`95-MIM`%dr zk)->oYW))+g`j*YgT>Ydw~6<@XmvkN`D)lCH_nwHWC1V!JdQQab!U<#DJl7roH{FPzM6)V(O$yvqjnkoUIS1hju2hF3+} z_OlzQ?@iTMg2|dTOY?|p`^kEo6uBv3JH}1if_&TSOzO7T#&jz|(g@$ya1fklxFJVZsYfHPl&bvF95#yFfqT>6`Sz@l*YhcJThj76WjAtE}A9er>fm$09d zVuoLUCC8wo7f~c1g23_aRRR%una=F$x)67}EYUHY`UF9sbdBRH5CQp5mgnT;H#M(A z7jFKsKO6PDou+N_-LA;m+OjqfEKhVrXyWmuij_HW098fGhGkRu>{5en3-c7_p_hYo zm|d5Iim-h2Wl-CD+?v>f+sgx=vNZ8l678jUSaA3k1e8OO!c^kn^Y$cgBL)D{maGmB zE3f2uLSFlr&^6VDmXbE9*t2KJFNy8$)~+}QKU1jDJ%=EBp~ObBljiFl7!LPMFHacv zA7Pd;(A!ek(r}P4>f=s+7tMCaHMvj(@;CYsZ~1n!rcG|AbjV=* z94xr}Yj(Zl14Axz$CPX6{1PC*A=aR0LOHH!Lq@R4igougQn-L<$q^ zGzRqE_XfTYa#5fP4&_NrDB0S@!D6W(4^iAg2&B6xjUM!#&2Az~XMt{%ul=pz+g$93 z*X0mse0mAF^s!(Q3Ybl^(bPm9HPl(?mE@X4q~z=P7+GlLS%??4@LCj=MB55d7;{6? z#lmPr3~ozicx5dcWC(Xgvu~Mh#23-5oq2`Z%%!Kx`dsKckyq!gU^v`iVlfcI%3!(I^M99Ks}6j@B@Y9p(1C&O=PB8<(#om^g^ z2F=17#)2OqY0N7ZZycLvm=KQ1@<7+V6o}ZWl(r`SvIa8!yyRi@VQk zgZ1W>Mp-(D$n)uozuo&Cxdd$0OHtV-Al%v7Gav5^ynSW7UiYbM)#`aYZ3<@9voJoI zIP$<}D<~)#nRUyq_c+e`Ud`7fRvr7lntOEKHfa3FzlD3~@R6S9;&qZ#`M*gJ$SC=F zy?rbxxA(xAIbqVff%)1Tn%9@UK2>(MP?S@mL!&0`a&|eLAh4ECJ#zptPJZ*8Eu2Hj z5Xo;VzyBV;%qnw!j*Yx+YwgIcqwU(d5k8@}PZF|ZDgG3*=E1lMzJ2NBZ!m7*V9uTO z%C3FTRLAq{y+_ekqXtt;?$`bcdyB07*EvQlrqANYK8bn#(7(t$*`}ePVS42r)6s7@ zfxj+`z>CPkIzvdd=iD5zU_VOCs84kf@d*E`@jcZ+Abi3CwEAT>!kH*XP5e5g`A6X5 zn#AW!%f@ank8lX8)KHx{Gg*Wa{}l}2D5W?^fCzaSfuwY~d*jdB{68!p>Z^c_4?-yu zuSR3MltI6fPBsRVR)d3(Gbg8S*NHAC0X9Yog%Icv(%#p?w$)zmbUB-DGjk%S&#Mmj51aCv~Zy1}Xks zad%WQ6M9?g;q$|VdpZsjc!V6Zl{x&83Uk%mWzp*QTQj=(@0v)c7P`!$oLkL%eD@B{ zO4ww-aV6m5;Wk=qNnU23C@*EI5*-YQGUvzUuWuF$Nwf-|&FBR=Gd>aZLNJ~iK*Nwt zYHY>b?}}it5>#y4^KU;ZiC*3;#dw+=O;xnJ+n)FjY2JZ`3^`ViklLhYH}H{x;PqB< z>;@1fJzHWz@ITO91f<#O+ygo*1vjt?W6?eUcOiu2)sVK4q67kct(n5Ohd>ILH{GnWr+FA5RtgRT-M_vuo_l69F%@R%pfRuRb<%Zuh#WM(7I1U49VzKOa3^td8 z%Xjq1c{5^qA&EGslziIso9rA~cQnH59Z(mX6iQzS*egU-j2dGYo|`VdJqZ^!h5^#` zw3?ZhyS~FpJp}6et#~rrGdCmQJ+;ZE)FEYY%oXoe*DnV%ljt+G4`FncrfbcJ zVeHX~pL1_he-`Zs+88SPF5y*#U6uE`VAX`fXwlChD~qb*?$f)lUz1kBbT0%BW6Y_E zj#zJQLyx|Tlg6zgB*YD7dY<;aNo^lu-Fo&uiohYLDx!V=BrVVB||T6{U-E_ zq9ev=_gDs2{_kn%a3CB*Y@gfla=z0Hb?LP(e!gU#QB1D*+n#u)n!@}BdIY*D-%Rd% z^R>!wWbRhLj-cfL-`@_;^&cek*qLIQj|YHKNFy2UcfV#4V+?2q`9La-y6v-L>j)LH zm2IbSfdG;-@q`V@K{936G8FN1NO`^tHW~^i+^#D&V7@FSj3Qj1!FhJk=0oUqbV->^ zQ11)y3vG|pd>*M|t^8_lN@1(y1iR0=EzdWF9}eR;!afA6T1v9|2KI$iw^opFYR;_o zM7fbMfx5?#eHg-HRtvY;11w--8?gsfHg-%;i%b!n=~ z!b#FA_bH(WD0+09g+?~-37Jx+KuiAc{Z0Pt$d%QH_kwj`gU3HwYP++^cafH;6Sn%v zu;#GULvY8)Rj|ENL1c|R0zwL#2m)ue&c*6za-_A_aNyx}Xv!*mEzQS4Yv$&EUgmo2DPWetWal4Fw?+mQ_6bdm=>U@17kA_x(8pBs zF~bp+KFTJJN2_M}Is96j07aPjXyA91tLUE0e(X=NbC-@>T;X^H zm8?h#hd8(Ck)dj;t4uIFub~m?ltB=SZ_}J-*QtsQeFP|F{=;+RrZp51zvj;<22MYQBsHZjUr%k{*-+NBeAK_vtr_UiX_>H1iRl#G`n~CYgE~q zvFc&;{tN3}AZ+rRkWxvBl$+qdC<&VZNLC_fu7LiAom1K#R{iDSO*tPt8&{}8;g%CO-T9V6Qz?47_uN6==y|ITMpTzaWXIpg3sy=!~Y)m ztauk7s46e1-kQ4#{H?tr&iaMR#1;;F7R0t*0KQH$4A1>m$e~}=W$b0tfA82hQ)5Qd zNHf(nJkQwSoi)vp6P!m3Lr=tFAJ4}uYe)_!mHJT+Df#4OP$faYFWqVl&Tv}MvjrvB zJIyigCfml%^fr7fMrN^K(V9HozL7+B9I|e3+nCMmS)t_JgWUR;(!JQU&D{hOQJ0cVQPEUc^)^4bZ|*m%ziLe0mDm2`H>5RtUm&b4(gau_ z^z`sq7|FRyDnO4$#acy)rGsXD7$C$55T52e)Vv5tVj*X%xYNAEz`9%xNLosGIa;yi za;TCk7P`m zlDJBtRbMUCv8ZlBC}RC%Td5`?S@KR_TblX{K#SP@UFU%8uo}t=1mvo}%=aU&(LcPDOY!lr9f!kBlRUP|$MDv2 zNzD`fW40nsk%YZDjmQvu(+9*0e>jp4ie2t|I2$MsE=rOwt+A{WxzIHh@}NHeB~-Pe zF~9PIcw&)5u}OTE-D2vTmQ;cA(K`)zW!3-|jPofai}Khw*=3odKC)*Woi%8u+GEA3w|rO;*ys^BGh}up6Gr6eJ_sv zfPGlEOrHS|mFTi%XwXFvY3&d6Vjb3IWv6b9Wh(s+*i(crm131;;R{y)tTjgjbKL#0 zhC74ksR`{^zz94f!+QqMnngp9p~p7z%5LsRdGhMY;8qZR(`udi}4`1w1J zM(+xLX{H-q8lL=$Yw|3ds|JDyF@R8|Uxs7EQZM(dxg45O9=aHdQNX;1dwkfCsDR!0 zS3x8;8;C!Ds!fB2%Tco*o=YpdL1r=bZ42(D9Y|z~gYnLM4XsEw_t2!q_sv`WDK}ew zSnGT67tg&Umv;z(0PhX&za=zr=19SV` zPpASS@;kt;yfcy6S!684B$8F`SJj`skcA?QE$OZCYWu6lIv+l^u1J?>$1aA|*BCYO zKyg@|AS!kBX~fm*#adjt9UeyJYAThFUEV&mE;%%|5B9bKBCQcTnGiYCQZ&WWFrSn} z+ZP^p7`Wyw7MYs_wq18M6awl4yHzI{!{EY+jtP zPP0*+rBEd5f=-fE&)82kmqvR3slwDotI8K;a_@_(A7?vh6r7VGWWkh>ovsAlH0tV7 zavP*cYe1p{$m!JfJP*^!YBpp8j_DT38nA60m_{lk9wr75b0kLx74xt+H>0Us?1fXu zUf?YhuuME$F9`n(Gd~`!umrc=L~|e*@gPbHi7*+=kN(mGMz2ABcHil5{LACkrJLZ^ z)JaYxU{gSX!qZat+HSggQbzTK*Udl@9o?_Q)C1#_;6$mR6uC5J9>v6yNNBHrKte|u z?@)x>U3_Ym1rHy2kTiB~Pj@khpG1EbY%bT`c6}y2H~(7fCsfinJp(l%nU5qp@p)Fs z&}SAOSKBk(&!9lusxm7;mi-RzOOQIZ~LYose4?BqE1G=p2T_%q>_2ll&&O3RbrB<|o z{zR$(VA4*s*rg^!E3y%O-gY56-#}n2oQ5OJEPes8!gEvq0=HHR#J-_e&C$?bR=CU^ zbzZLGjY9~O&)CX1x&BG9A!c&vdT;BE+|LIxEDz-k?~2xtg=Gb-U#j(HaC1}-M;k^9 zZ+f46>eF#!H2waJnL%|ep7H(zL7GW-N;_9h}5R?|MXzbmkuAT@}3hK^UK@}eml93G>AzpwWbgr3+X1ub)6XMt%p%` zi&m>e^@JAxu#2>J83BA%VlkYnc4L_&QGP48=ZALFvMW`8cw1{p^LAR`d+X!cqcxTe zxUG7#m~UmrG__w3@}Zs*d^8_ORI?M^nwIk?I4uqkAq&O z;8*!Zarv5~$Sf9LUNi&bKP7&pvgGG6GKNTJ2w#k8Q-A1x5UNQ(u0?rHAI3#sze||< zR*`47^WsYU+iGfhWCY3gjYqcQHZh?IGjF$uK&b@%I(rRbW*&TpI`VSM_z7S_?km3` zC$PK3?oAG~`+Nq-LLYv%)Ch>M*l1@t{Y1AMoL;bael>KU*5BX6ZxiP9Ged!2$@bCH z+eD_5f1cYN*TLNXI&MI(ce~eq7$h>B@N$Y^<$55CS5-u!iO;dg;jA!JMnX<=YS3Rq zbvv5?PbBJklZOp=Ii16VkgNTCO3<&X9@xc2hlxs(u?Wr$g@C*)cTP#|2Z9hY}x7hYP>2*d_3qJjW@60oe7XcAu?402YDZhAN&H~IoiAwVLhBY=Q1UGr{Tyb8G;%f&x1&HllxR*(9B0Tw%-6Yi?y|%+ts65Jme(@ToVr9xVr0nNAfJ zH6%5VN)p#Ol+KrNEw9Y^H`u14^?n0j#Z64t&T~QjHzGOO2^AAbl(eeZ{(e{8>7qrum*@OHhh8t6N503^w-YrOT{ib; z`R@OrOzmK2+vWUWl-~S?v;SGyuloW8^GG2Pjlk{StczVO1RF61i~$b~EawOFLYM>& z6jC1p=HntXq_TMnn$$KswbVof4h=Qfo$Weg{z~(R9?ymGz?6{lzJ($FS^(Eatr7}9 zb5!we*5U;MNDR{cF)2@m*hJs^2+dJ8* z?m^WdlI8$mv`J2H@5J+W5B!Ty^66L^>SAk~`s!p*iHpP?tcT|+ABvN+G+F<}?W;fg zHG>t^nKs=NU7lGaSl4AihyQRj^%QLI{KI*pzPG_k&&}-Rh%*D)FTeeY{p{S#?CXB=qCWoT|;(T)QY}|ytjYy5$Y@jdb+j^3k*B4W;=Q3^V4TH0lZBH zC2Q@d{^vDw(}~QT25pi!N}V?|g1#EAgT@K<%1sNJKB8{gJ)Qi|CF#R@RCAZoi>WVI zPC@_P2~VW$>;Pxu@a~59d-IiemG>LDf0$7Mup zuv0S<^XbF5uu#rRPy46eNGQg6nh2jX{`=tmW1Or)i0Q>Cq9MvzzySX>^nZVdiq7gv z9BNJcukQWV5yL9ru*^Y+dzqJrGVr(5)|r=K3?E~5|&4`)dhs{SusK?2$^!F+N5<18@JCUb6=#^D{W z*Z`tP(Er^n1Vq$Cq~wHW|7UNXh6ZlN hV0Hcf^lj`4LDUmpiE0QBzCr;0-pH!Hu8=kl|2O!2V#ojh literal 0 HcmV?d00001 diff --git a/app/__init__.py b/app/__init__.py index ae00aed39..012585bc6 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -5,6 +5,9 @@ from app.routes.goal_routes import bp as goals_bp import os +from dotenv import load_dotenv +load_dotenv() + def create_app(config=None): app = Flask(__name__) diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py index 16bc5f0a3..2624d4098 100644 --- a/app/routes/task_routes.py +++ b/app/routes/task_routes.py @@ -1,8 +1,9 @@ from ..models.task import Task -from flask import Blueprint, abort, make_response, request, Response,jsonify +from flask import Blueprint, abort, make_response, request, Response,jsonify,current_app from ..db import db from .route_utilities import validate_model,create_model,get_models_with_filters from datetime import datetime +import requests,os bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @@ -104,12 +105,12 @@ def del_task(id): -@bp.patch("//mark_complete") -def mark_complete(id): - task = validate_model(Task, id) - task.completed_at = datetime.utcnow() - db.session.commit() - return Response(status=204, mimetype="application/json") +# @bp.patch("//mark_complete") +# def mark_complete(id): +# task = validate_model(Task, id) +# task.completed_at = datetime.utcnow() +# db.session.commit() +# return Response(status=204, mimetype="application/json") @bp.patch("//mark_incomplete") @@ -119,3 +120,32 @@ def mark_incomplete(id): task.completed_at = None db.session.commit() return Response(status=204, mimetype="application/json") + + + +@bp.patch("//mark_complete") +def mark_task_complete(id): + task = validate_model(Task, id) + #task = Task.query.get(id) + + + task.completed_at = datetime.utcnow() + db.session.commit() + + + slack_token = os.environ.get("SLACK_BOT_TOKEN") + slack_channel = os.environ.get("SLACK_CHANNEL", "#test_task_slack_api") + message = f"Task *{task.title}* has been completed!" + + if not current_app.config.get("TESTING"): + response = requests.post( + "https://slack.com/api/chat.postMessage", + headers={"Authorization": f"Bearer {slack_token}"}, + + json={"channel": slack_channel, "text": message} + ) + if current_app.config.get("TESTING"): + return Response(status=204, mimetype="application/json") + + + return jsonify({"task": task.to_dict()}), 200 \ No newline at end of file From eb9c1999573cb1462b14975f40a5d13da3097207 Mon Sep 17 00:00:00 2001 From: xile Date: Thu, 6 Nov 2025 22:21:04 -0800 Subject: [PATCH 4/4] datetime library change --- ada-project-docs/wave_04.md | 8 ++++++++ app/routes/task_routes.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ada-project-docs/wave_04.md b/ada-project-docs/wave_04.md index 85e5ad839..dda46e054 100644 --- a/ada-project-docs/wave_04.md +++ b/ada-project-docs/wave_04.md @@ -192,3 +192,11 @@ Send `PATCH` requests to `localhost:5000/tasks//mark_complete` (use the ![](assets/postman_patch.png) ![](assets/slack_notification_feature.png) + + + +The result show as below +![](assets/lexy-slackbot.png) + + + diff --git a/app/routes/task_routes.py b/app/routes/task_routes.py index 2624d4098..e848e2df3 100644 --- a/app/routes/task_routes.py +++ b/app/routes/task_routes.py @@ -2,7 +2,7 @@ from flask import Blueprint, abort, make_response, request, Response,jsonify,current_app from ..db import db from .route_utilities import validate_model,create_model,get_models_with_filters -from datetime import datetime +from datetime import datetime,timezone import requests,os bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") @@ -129,7 +129,7 @@ def mark_task_complete(id): #task = Task.query.get(id) - task.completed_at = datetime.utcnow() + task.completed_at = datetime.now(timezone.utc) db.session.commit()