From 5c26838c507b459f3c5c3410c855e89c4d48b016 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Tue, 22 Nov 2022 05:25:11 -0500
Subject: [PATCH 01/32] begin timezone support
---
app/db.py | 1 +
app/migrations/versions/d4e71a6e9479_.py | 28 ++++++++++++++++++++++++
2 files changed, 29 insertions(+)
create mode 100644 app/migrations/versions/d4e71a6e9479_.py
diff --git a/app/db.py b/app/db.py
index 6d380b0..27fe326 100644
--- a/app/db.py
+++ b/app/db.py
@@ -43,3 +43,4 @@ class User(UserMixin, db.Model):
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
realName = db.Column(db.String(1000))
+ timezone = db.Column(db.String(20))
diff --git a/app/migrations/versions/d4e71a6e9479_.py b/app/migrations/versions/d4e71a6e9479_.py
new file mode 100644
index 0000000..14e1572
--- /dev/null
+++ b/app/migrations/versions/d4e71a6e9479_.py
@@ -0,0 +1,28 @@
+"""empty message
+
+Revision ID: d4e71a6e9479
+Revises: bf65c9f77f9f
+Create Date: 2022-11-22 05:24:06.596342
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'd4e71a6e9479'
+down_revision = 'bf65c9f77f9f'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('user', sa.Column('timezone', sa.String(length=20), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column('user', 'timezone')
+ # ### end Alembic commands ###
--
2.49.1
From 8ed6336e208ef8994cba8263719c4ba452c7bbf7 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Wed, 23 Nov 2022 20:36:38 -0500
Subject: [PATCH 02/32] create settings page, full timezone support
---
app/app.py | 19 ++++++++++++---
app/db.py | 2 +-
app/forms.py | 8 ++++++-
.../{d4e71a6e9479_.py => d92ccc005d22_.py} | 8 +++----
app/misc.py | 1 +
app/requirements.txt | 3 +++
app/templates/events.html | 4 +++-
app/templates/settings.html | 23 +++++++++++++++++++
8 files changed, 58 insertions(+), 10 deletions(-)
rename app/migrations/versions/{d4e71a6e9479_.py => d92ccc005d22_.py} (80%)
create mode 100644 app/templates/settings.html
diff --git a/app/app.py b/app/app.py
index a7421dd..911034b 100644
--- a/app/app.py
+++ b/app/app.py
@@ -3,9 +3,9 @@ from flask import Flask, render_template, redirect, url_for, request, flash
from flask_migrate import Migrate
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import (LoginManager, login_user, login_required, logout_user, current_user)
-from misc import datetime, date, time, currDay
+from misc import datetime, date, time, currDay, ZoneInfo
from db import (db, Period, Task, Event, User)
-from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm)
+from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm, SettingsForm)
from create_events import createEvents
basedir = os.path.abspath(os.path.dirname(__file__))
@@ -82,6 +82,19 @@ def logout():
logout_user()
return redirect(url_for('index'))
+@app.route('/settings', methods=('GET', 'POST'))
+@login_required
+def settings():
+ user = User.query.get_or_404(current_user.id)
+ form = SettingsForm(obj=user)
+ if form.validate_on_submit():
+ user.realName = form.realName.data
+ user.timezone = form.timezone.data
+ if form.password.data != '':
+ user.password = generate_password_hash(form.password.data, method='sha256')
+ db.session.commit()
+ return render_template('settings.html', form=form)
+
# Periods routes
@app.route('/periods')
@login_required
@@ -133,7 +146,7 @@ def events():
periods = Period.query.all()
createEvents(db, currDay, Period, Event)
- return render_template('events.html', events=events, periods=periods, datetime=datetime, date=date)
+ return render_template('events.html', events=events, periods=periods, datetime=datetime, date=date, ZoneInfo=ZoneInfo)
@app.route('/event/edit//', methods=('GET', 'POST'))
@login_required
diff --git a/app/db.py b/app/db.py
index 27fe326..4bb6bff 100644
--- a/app/db.py
+++ b/app/db.py
@@ -43,4 +43,4 @@ class User(UserMixin, db.Model):
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
realName = db.Column(db.String(1000))
- timezone = db.Column(db.String(20))
+ timezone = db.Column(db.String(20), default='UTC')
diff --git a/app/forms.py b/app/forms.py
index 928c5fb..8548404 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -1,6 +1,7 @@
+import pytz
from db import Task
from flask_wtf import FlaskForm
-from wtforms import (StringField, DateField, TimeField, TextAreaField, IntegerField, BooleanField,
+from wtforms import (StringField, DateField, TimeField, TextAreaField, IntegerField, SelectField, BooleanField,
RadioField, EmailField, PasswordField)
from wtforms.validators import InputRequired, Length
from wtforms_sqlalchemy.orm import QuerySelectField
@@ -21,6 +22,11 @@ class PeriodForm(FlaskForm):
weekendSchedule = BooleanField(label='Include on Weekends?', false_values=None)
periodTime = TimeField('Time', format="%H:%M")
+class SettingsForm(FlaskForm):
+ password = PasswordField('Password')
+ realName = StringField('Real Name')
+ timezone = SelectField('Time Zone', choices=pytz.all_timezones)
+
class SignupForm(FlaskForm):
userName = StringField('Username', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()])
diff --git a/app/migrations/versions/d4e71a6e9479_.py b/app/migrations/versions/d92ccc005d22_.py
similarity index 80%
rename from app/migrations/versions/d4e71a6e9479_.py
rename to app/migrations/versions/d92ccc005d22_.py
index 14e1572..fcd70ff 100644
--- a/app/migrations/versions/d4e71a6e9479_.py
+++ b/app/migrations/versions/d92ccc005d22_.py
@@ -1,8 +1,8 @@
"""empty message
-Revision ID: d4e71a6e9479
+Revision ID: d92ccc005d22
Revises: bf65c9f77f9f
-Create Date: 2022-11-22 05:24:06.596342
+Create Date: 2022-11-23 20:32:41.868230
"""
from alembic import op
@@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
-revision = 'd4e71a6e9479'
+revision = 'd92ccc005d22'
down_revision = 'bf65c9f77f9f'
branch_labels = None
depends_on = None
@@ -18,7 +18,7 @@ depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
- op.add_column('user', sa.Column('timezone', sa.String(length=20), nullable=True))
+ op.add_column('user', sa.Column('timezone', sa.String(length=20), nullable=True, server_default='UTC'))
# ### end Alembic commands ###
diff --git a/app/misc.py b/app/misc.py
index 9124938..b0933f7 100644
--- a/app/misc.py
+++ b/app/misc.py
@@ -1,4 +1,5 @@
import time
from datetime import datetime, date
+from zoneinfo import ZoneInfo
currDay = datetime.now()
currDay = currDay.strftime('%m-%d-%Y')
\ No newline at end of file
diff --git a/app/requirements.txt b/app/requirements.txt
index 8ed3ceb..1158712 100644
--- a/app/requirements.txt
+++ b/app/requirements.txt
@@ -20,6 +20,9 @@ Flask-User==1.0.2.2
# WTForms Extensions
WTForms-SQLAlchemy==0.3.0
+# Python Time packages
+pytz==2022.6
+
# Automated tests
pytest==7.2.0
pytest-cov==4.0.0
diff --git a/app/templates/events.html b/app/templates/events.html
index b4b1c0d..fc9cfe1 100644
--- a/app/templates/events.html
+++ b/app/templates/events.html
@@ -1,10 +1,12 @@
{% extends 'base.html' %}
{% set currDay = datetime.now() %}
+{% set currDay = currDay.astimezone(ZoneInfo(current_user.timezone)) %}
+{% set currTime = currDay.strftime('%I:%M %p') %}
{% set currDay = currDay.strftime('%m-%d-%Y') %}
{% block content %}
{% block title %} Events {% endblock %}
- Current Date: {{ currDay }}
+ Current Date: {{ currDay }}
Current Time: {{ currTime }}
{% for period in periods %}
diff --git a/app/templates/settings.html b/app/templates/settings.html
new file mode 100644
index 0000000..04c0611
--- /dev/null
+++ b/app/templates/settings.html
@@ -0,0 +1,23 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
{% block title %} Settings {% endblock %}
+
+{% endblock %}
\ No newline at end of file
--
2.49.1
From ec3120612755ff81f09caa231acf0ea93f6cc779 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Thu, 24 Nov 2022 13:36:37 -0500
Subject: [PATCH 03/32] Add settings page, fix responsive login/logout link
---
app/templates/base.html | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/app/templates/base.html b/app/templates/base.html
index ba71787..3c17a12 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -17,7 +17,7 @@
-
--
2.49.1
From cc57dfdb325c8cf8ef6e28ca4424ab1948383867 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Thu, 24 Nov 2022 18:07:36 -0500
Subject: [PATCH 04/32] change flask to use port 80
---
Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index bc2d72d..4c3adc7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,4 +3,4 @@ COPY ./app /app
WORKDIR /app
RUN pip3 install -r requirements.txt
ENV FLASK_APP=app.py
-CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0"]
\ No newline at end of file
+CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0", "--port=80"]
\ No newline at end of file
--
2.49.1
From 00c4bc960e4ac57779a0b8a7f35f3a91a3796e9d Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Thu, 24 Nov 2022 19:06:42 -0500
Subject: [PATCH 05/32] prevent app from crashing when SIGNUP_ENABLED env
variable does not exist
---
app/app.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/app/app.py b/app/app.py
index 911034b..f1d61b0 100644
--- a/app/app.py
+++ b/app/app.py
@@ -27,8 +27,11 @@ login_manager = LoginManager()
login_manager.login_view = 'login'
login_manager.init_app(app)
-if os.environ['SIGNUP_ENABLED'] == "YES":
- signup_enabled = True
+if 'SIGNUP_ENABLED' in os.environ:
+ if os.environ['SIGNUP_ENABLED'] == "YES":
+ signup_enabled = True
+ else:
+ signup_enabled = False
else:
signup_enabled = False
--
2.49.1
From ac8bed1bad7c7e1641ccd362217a5c94222f9908 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Fri, 25 Nov 2022 15:01:13 -0500
Subject: [PATCH 06/32] add footer and support showing version/commit
---
app/__commit__ | 1 +
app/__version__ | 1 +
app/app.py | 7 ++++++-
app/misc.py | 7 ++++++-
app/templates/base.html | 6 ++++--
5 files changed, 18 insertions(+), 4 deletions(-)
create mode 100644 app/__commit__
create mode 100644 app/__version__
diff --git a/app/__commit__ b/app/__commit__
new file mode 100644
index 0000000..87edf79
--- /dev/null
+++ b/app/__commit__
@@ -0,0 +1 @@
+unknown
\ No newline at end of file
diff --git a/app/__version__ b/app/__version__
new file mode 100644
index 0000000..7f20734
--- /dev/null
+++ b/app/__version__
@@ -0,0 +1 @@
+1.0.1
\ No newline at end of file
diff --git a/app/app.py b/app/app.py
index f1d61b0..257bf35 100644
--- a/app/app.py
+++ b/app/app.py
@@ -3,7 +3,7 @@ from flask import Flask, render_template, redirect, url_for, request, flash
from flask_migrate import Migrate
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import (LoginManager, login_user, login_required, logout_user, current_user)
-from misc import datetime, date, time, currDay, ZoneInfo
+from misc import datetime, date, time, currDay, ZoneInfo, currVersion, currCommit
from db import (db, Period, Task, Event, User)
from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm, SettingsForm)
from create_events import createEvents
@@ -39,6 +39,11 @@ else:
def load_user(user_id):
return User.query.get(int(user_id))
+# Context processor injects current version and commit
+@app.context_processor
+def injectVerCommit():
+ return dict(currVersion=currVersion, currCommit=currCommit)
+
# Index route
@app.route('/')
def index():
diff --git a/app/misc.py b/app/misc.py
index b0933f7..cfc9589 100644
--- a/app/misc.py
+++ b/app/misc.py
@@ -2,4 +2,9 @@ import time
from datetime import datetime, date
from zoneinfo import ZoneInfo
currDay = datetime.now()
-currDay = currDay.strftime('%m-%d-%Y')
\ No newline at end of file
+currDay = currDay.strftime('%m-%d-%Y')
+
+with open('__version__','r') as file:
+ currVersion = file.read()
+with open('__commit__','r') as file:
+ currCommit = file.read()
\ No newline at end of file
diff --git a/app/templates/base.html b/app/templates/base.html
index 3c17a12..983983f 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -10,7 +10,7 @@
{% block title %} {% endblock %} - BellScheduler
-
+
{% endfor %}
+
{% endblock %}
\ No newline at end of file
--
2.49.1
From 130825d3dca2bb050c4c6898d5d9f8daa0fbe6ea Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Sat, 26 Nov 2022 11:57:08 -0500
Subject: [PATCH 11/32] re-add createEvents script to newPeriod function
---
app/app.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/app.py b/app/app.py
index f8971a7..96edaca 100644
--- a/app/app.py
+++ b/app/app.py
@@ -124,6 +124,8 @@ def newPeriod():
)
db.session.add(period)
db.session.commit()
+ # Run createEvents upon adding new period
+ createEvents(db, currDay, Period, Event)
return redirect(f'/period/edit/{period.period}')
return render_template('newPeriod.html', form=form)
--
2.49.1
From bf539fca09414a7893dcd472b96726dc19aebea5 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Tue, 29 Nov 2022 18:12:55 -0500
Subject: [PATCH 12/32] create separate worker app for background tasks
---
app/app.py | 5 ++---
app/backgroundTasks.py | 14 --------------
app/worker.py | 31 +++++++++++++++++++++++++++++++
3 files changed, 33 insertions(+), 17 deletions(-)
delete mode 100644 app/backgroundTasks.py
create mode 100644 app/worker.py
diff --git a/app/app.py b/app/app.py
index 96edaca..6d7ecec 100644
--- a/app/app.py
+++ b/app/app.py
@@ -6,7 +6,6 @@ from flask_login import (LoginManager, login_user, login_required, logout_user,
from misc import datetime, date, time, currDay, ZoneInfo, currVersion, currCommit
from db import (db, Period, Task, Event, User)
from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm, SettingsForm)
-from backgroundTasks import scheduleCreateEvents
from create_events import createEvents
basedir = os.path.abspath(os.path.dirname(__file__))
@@ -23,8 +22,8 @@ migrate = Migrate()
migrate.init_app(app, db)
# Schedule creation of events every hour
-with app.app_context():
- scheduleCreateEvents(app, db, currDay, Period, Event, createEvents)
+#with app.app_context():
+# scheduleCreateEvents(app, db, currDay, Period, Event, createEvents)
# Authentication stuff
diff --git a/app/backgroundTasks.py b/app/backgroundTasks.py
deleted file mode 100644
index f8d6458..0000000
--- a/app/backgroundTasks.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from apscheduler.schedulers.background import BackgroundScheduler
-from apscheduler.triggers.cron import CronTrigger
-
-def scheduleCreateEvents(app, db, currDay, Period, Event, createEvents):
- # create events upon application launch
- createEvents(db, currDay, Period, Event)
-
- # schedule createEvents task every hour
- sched = BackgroundScheduler()
- def eventsTask():
- with app.app_context():
- createEvents(db, currDay, Period, Event)
- sched.add_job(eventsTask, CronTrigger.from_crontab('00 * * * *'))
- sched.start()
\ No newline at end of file
diff --git a/app/worker.py b/app/worker.py
new file mode 100644
index 0000000..f9de303
--- /dev/null
+++ b/app/worker.py
@@ -0,0 +1,31 @@
+import os
+from flask import Flask
+from misc import currDay
+from db import db, Period, Event
+from create_events import createEvents
+from apscheduler.schedulers.background import BlockingScheduler
+from apscheduler.triggers.cron import CronTrigger
+
+basedir = os.path.abspath(os.path.dirname(__file__))
+
+app = Flask(__name__)
+app.config['SECRET_KEY'] = 'HwG55rpe83jcaglifXm8NuF4WEeXyJV4'
+app.config['SQLALCHEMY_DATABASE_URI'] =\
+ 'sqlite:///' + os.path.join(basedir, os.environ['SQLITE_DB'])
+app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+db.init_app(app)
+
+# declare BlockingScheduler
+sched = BlockingScheduler()
+
+# run createEvents upon app launch
+with app.app_context():
+ createEvents(db, currDay, Period, Event)
+
+def eventsTask():
+ with app.app_context():
+ createEvents(db, currDay, Period, Event)
+sched.add_job(eventsTask, CronTrigger.from_crontab('00 * * * *'))
+print("Background worker started")
+sched.start()
+
--
2.49.1
From dfa8eed12d836f912ca33b54dbbff754b2376212 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Tue, 29 Nov 2022 20:41:56 -0500
Subject: [PATCH 13/32] remove hardcoded secret key, rely on env variable
---
app/app.py | 2 +-
app/worker.py | 2 +-
docker-compose.yaml | 1 +
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/app.py b/app/app.py
index 6d7ecec..09ace99 100644
--- a/app/app.py
+++ b/app/app.py
@@ -11,7 +11,7 @@ from create_events import createEvents
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
-app.config['SECRET_KEY'] = 'HwG55rpe83jcaglifXm8NuF4WEeXyJV4'
+app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] =\
'sqlite:///' + os.path.join(basedir, os.environ['SQLITE_DB'])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
diff --git a/app/worker.py b/app/worker.py
index f9de303..3593008 100644
--- a/app/worker.py
+++ b/app/worker.py
@@ -9,7 +9,7 @@ from apscheduler.triggers.cron import CronTrigger
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
-app.config['SECRET_KEY'] = 'HwG55rpe83jcaglifXm8NuF4WEeXyJV4'
+app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] =\
'sqlite:///' + os.path.join(basedir, os.environ['SQLITE_DB'])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 889859d..90e42b2 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -5,6 +5,7 @@ services:
restart: always
environment:
- SQLITE_DB=database.db
+ - SECRET_KEY=notasecuresecretkeyonlyuseforlocaldevelopment
volumes:
- ./database.db:/app/database.db
ports:
--
2.49.1
From ddc90da0127036f8a39a56aa2e457881e8570709 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Tue, 29 Nov 2022 20:55:32 -0500
Subject: [PATCH 14/32] Add "remember me" option to login
---
app/app.py | 3 ++-
app/forms.py | 3 ++-
app/templates/login.html | 3 +++
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/app/app.py b/app/app.py
index 09ace99..6c9f3b8 100644
--- a/app/app.py
+++ b/app/app.py
@@ -60,12 +60,13 @@ def login():
if form.validate_on_submit():
userName = form.userName.data
password = form.password.data
+ remember = form.rememberMe.data
user = User.query.filter_by(userName=userName).first()
if not user or not check_password_hash(user.password, password):
flash('Credentials incorrect! Please try again')
return redirect(url_for('login'))
- login_user(user)
+ login_user(user, remember=remember)
return redirect(url_for('events'))
return render_template('login.html', form=form)
diff --git a/app/forms.py b/app/forms.py
index 8548404..e276de7 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -35,4 +35,5 @@ class SignupForm(FlaskForm):
class LoginForm(FlaskForm):
userName = StringField('Username', validators=[InputRequired()])
- password = PasswordField('Password', validators=[InputRequired()])
\ No newline at end of file
+ password = PasswordField('Password', validators=[InputRequired()])
+ rememberMe = BooleanField(label='Remember me?')
\ No newline at end of file
diff --git a/app/templates/login.html b/app/templates/login.html
index ed2c19e..a2df10d 100644
--- a/app/templates/login.html
+++ b/app/templates/login.html
@@ -19,6 +19,9 @@
{{ form.password.label }}
{{ form.password }}
+
+ {{ form.rememberMe.label}} {{ form.rememberMe }}
+
--
2.49.1
From 727941edd3728d6ac95aa77aa7bb20f5228cca04 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Tue, 29 Nov 2022 22:23:01 -0500
Subject: [PATCH 15/32] Error handling and custom error pages
---
app/app.py | 20 +++++++++++++++++++-
app/templates/errors/403.html | 22 ++++++++++++++++++++++
app/templates/errors/404.html | 22 ++++++++++++++++++++++
app/templates/errors/500.html | 22 ++++++++++++++++++++++
4 files changed, 85 insertions(+), 1 deletion(-)
create mode 100644 app/templates/errors/403.html
create mode 100644 app/templates/errors/404.html
create mode 100644 app/templates/errors/500.html
diff --git a/app/app.py b/app/app.py
index 6c9f3b8..0da03e0 100644
--- a/app/app.py
+++ b/app/app.py
@@ -46,7 +46,25 @@ def load_user(user_id):
# Context processor injects current version and commit
@app.context_processor
def injectVerCommit():
- return dict(currVersion=currVersion, currCommit=currCommit)
+ return dict(currVersion=currVersion, currCommit=currCommit, datetime=datetime, ZoneInfo=ZoneInfo)
+
+# Error handling
+# Pass env variables for debugging in prod
+NODE_NAME = os.environ['NODE_NAME']
+POD_NAME = os.environ['POD_NAME']
+
+# Error Routes
+@app.errorhandler(404)
+def notFound(e):
+ return render_template('errors/404.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 404
+
+@app.errorhandler(403)
+def forbidden(e):
+ return render_template('errors/403.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 403
+
+@app.errorhandler(500)
+def ISEerror(e):
+ return render_template('errors/500.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 500
# Index route
@app.route('/')
diff --git a/app/templates/errors/403.html b/app/templates/errors/403.html
new file mode 100644
index 0000000..5b703b1
--- /dev/null
+++ b/app/templates/errors/403.html
@@ -0,0 +1,22 @@
+{% extends 'base.html' %}
+{% set currDay = datetime.now() %}
+{% set currTime = currDay.strftime('%I:%M %p') %}
+{% set currDay = currDay.strftime('%m-%d-%Y') %}
+
+{% block content %}
+ {% block title %} Error 403 {% endblock %}
+
+
+
Forbidden
+
+
+
+ Date: {{ currDay }}
+ Time {{ currTime }} (UTC)
+
+
+ Node: {{ NODE_NAME }}
+ Pod: {{ POD_NAME }}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/templates/errors/404.html b/app/templates/errors/404.html
new file mode 100644
index 0000000..a6fa5f8
--- /dev/null
+++ b/app/templates/errors/404.html
@@ -0,0 +1,22 @@
+{% extends 'base.html' %}
+{% set currDay = datetime.now() %}
+{% set currTime = currDay.strftime('%I:%M %p') %}
+{% set currDay = currDay.strftime('%m-%d-%Y') %}
+
+{% block content %}
+ {% block title %} Error 404 {% endblock %}
+
+
+
Page Not Found
+
+
+
+ Date: {{ currDay }}
+ Time {{ currTime }} (UTC)
+
+
+ Node: {{ NODE_NAME }}
+ Pod: {{ POD_NAME }}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/templates/errors/500.html b/app/templates/errors/500.html
new file mode 100644
index 0000000..690f0d3
--- /dev/null
+++ b/app/templates/errors/500.html
@@ -0,0 +1,22 @@
+{% extends 'base.html' %}
+{% set currDay = datetime.now() %}
+{% set currTime = currDay.strftime('%I:%M %p') %}
+{% set currDay = currDay.strftime('%m-%d-%Y') %}
+
+{% block content %}
+ {% block title %} Error 500 {% endblock %}
+
+
+
Internal Server Error
+
+
+
+ Date: {{ currDay }}
+ Time {{ currTime }} (UTC)
+
+
+ Node: {{ NODE_NAME }}
+ Pod: {{ POD_NAME }}
+
+
+{% endblock %}
\ No newline at end of file
--
2.49.1
From 71fa0bec22cc1c6eade56b552f752b462d2a4618 Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Tue, 29 Nov 2022 22:51:13 -0500
Subject: [PATCH 16/32] change docker-compose for new env variables
---
docker-compose.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 90e42b2..8513e4f 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -6,6 +6,8 @@ services:
environment:
- SQLITE_DB=database.db
- SECRET_KEY=notasecuresecretkeyonlyuseforlocaldevelopment
+ - NODE_NAME=local
+ - POD_NAME=local
volumes:
- ./database.db:/app/database.db
ports:
--
2.49.1
From 645fc4a56b26d5157d0f98ab3db68b5d25c84d8d Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Wed, 30 Nov 2022 00:46:17 -0500
Subject: [PATCH 17/32] docker-compose file update 2 electric boogalo
---
docker-compose.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 8513e4f..2ee00c2 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -8,6 +8,9 @@ services:
- SECRET_KEY=notasecuresecretkeyonlyuseforlocaldevelopment
- NODE_NAME=local
- POD_NAME=local
+ - FLASK_ENV=development
+ - FLASK_DEBUG=1
+ - PYTHONUNBUFFERED=1
volumes:
- ./database.db:/app/database.db
ports:
--
2.49.1
From a441b4612a285ae8f81b82cbd08160d7982f8cfb Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Wed, 30 Nov 2022 01:09:22 -0500
Subject: [PATCH 18/32] implement gunicorn for prod WSGI server
---
Dockerfile | 2 +-
app/requirements.txt | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index 4c3adc7..c77976c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,4 +3,4 @@ COPY ./app /app
WORKDIR /app
RUN pip3 install -r requirements.txt
ENV FLASK_APP=app.py
-CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0", "--port=80"]
\ No newline at end of file
+CMD ["gunicorn", "-b", "0.0.0.0:80", "-w", "4", "app:app"]
\ No newline at end of file
diff --git a/app/requirements.txt b/app/requirements.txt
index fb83cc6..b935201 100644
--- a/app/requirements.txt
+++ b/app/requirements.txt
@@ -1,6 +1,9 @@
# This file is used by pip to install required python packages
# Usage: pip install -r requirements.txt
+# Gunicorn WSGI Server
+gunicorn==20.1.0
+
# Flask Framework
click==8.1.3
Flask==2.2.2
--
2.49.1
From f15d145144783e43eee887881e1d0fa3586647ac Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Thu, 1 Dec 2022 00:54:41 -0500
Subject: [PATCH 19/32] add prevDay variable for previous day, primarily for
queries
---
app/app.py | 2 +-
app/misc.py | 9 ++++++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/app/app.py b/app/app.py
index 0da03e0..a26430f 100644
--- a/app/app.py
+++ b/app/app.py
@@ -3,7 +3,7 @@ from flask import Flask, render_template, redirect, url_for, request, flash
from flask_migrate import Migrate
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import (LoginManager, login_user, login_required, logout_user, current_user)
-from misc import datetime, date, time, currDay, ZoneInfo, currVersion, currCommit
+from misc import datetime, date, time, currDay, prevDay, ZoneInfo, currVersion, currCommit
from db import (db, Period, Task, Event, User)
from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm, SettingsForm)
from create_events import createEvents
diff --git a/app/misc.py b/app/misc.py
index cfc9589..f4667bb 100644
--- a/app/misc.py
+++ b/app/misc.py
@@ -1,9 +1,16 @@
import time
-from datetime import datetime, date
+from datetime import datetime, date, timedelta
from zoneinfo import ZoneInfo
+
+# Get current day in UTC and put it in str format
currDay = datetime.now()
currDay = currDay.strftime('%m-%d-%Y')
+# Get previous day in UTC and put it in str format
+prevDay = datetime.now() + timedelta(days=-1)
+prevDay = prevDay.strftime('%m-%d-%Y')
+
+
with open('__version__','r') as file:
currVersion = file.read()
with open('__commit__','r') as file:
--
2.49.1
From 2b315caf9e5bdbdc0921b8c958d013b728541edc Mon Sep 17 00:00:00 2001
From: William Peebles
Date: Thu, 1 Dec 2022 01:16:10 -0500
Subject: [PATCH 20/32] favicon support
---
app/static/android-chrome-192x192.png | Bin 0 -> 14923 bytes
app/static/android-chrome-512x512.png | Bin 0 -> 38829 bytes
app/static/apple-touch-icon.png | Bin 0 -> 13467 bytes
app/static/favicon-16x16.png | Bin 0 -> 816 bytes
app/static/favicon-32x32.png | Bin 0 -> 1910 bytes
app/static/favicon.ico | Bin 0 -> 15406 bytes
app/static/manifest.json | 1 +
app/templates/base.html | 4 ++++
8 files changed, 5 insertions(+)
create mode 100644 app/static/android-chrome-192x192.png
create mode 100644 app/static/android-chrome-512x512.png
create mode 100644 app/static/apple-touch-icon.png
create mode 100644 app/static/favicon-16x16.png
create mode 100644 app/static/favicon-32x32.png
create mode 100644 app/static/favicon.ico
create mode 100644 app/static/manifest.json
diff --git a/app/static/android-chrome-192x192.png b/app/static/android-chrome-192x192.png
new file mode 100644
index 0000000000000000000000000000000000000000..972695927845fcde30a363d0a3d74e2797dc644f
GIT binary patch
literal 14923
zcmV-RI<&=!P)PyA07*naRCr$Poe7v6Rkg=|)!nmYCdo`@NoKMW_9Q^qWk9wy>`W*~vCbGRf@IU0v_{S9i!{rfci2?&+D#dEXe`
z*SBund+MHh&ppfk7>rSv94Lk8F~Ehu!9WQR2l~M305*fM5!e9MI$$LjDp3vK59UL_`a+!w*`A$#W|U1EKjZNs0Dij3
zW_Y9WnKFo+2FCw@rOR+R5CKZT(0}VMTA-OHz<3doe}d#az>9!cpm&d$*Z2^C5i^%!
z8K@Tgq_inii(Nc6pezbj2_z?hH3h60DT|S@Gq5Mnlopn-=b<}+PJusyDHE;y5B_PC
z?|(b602JMqt%2AANFD&j+rZcZELoH$0OK+#|5?mn6IfS5@>jqbAn9KcMrX?%SO9YQ
zpjj-v;IFHOl3l^v6C(4$S^(?=)CBKpyC`tdvqKtUGDi2eXR
z2d|Kd>eS+o90ie+!BVJ2%>8-S3%4vsKL_(7Kw%o*Gzvdwo&~_0r^C`>1b@hFLoeMO
zlAi(I1J?AksuYx+{b0N=Z8i=1>@OqnF7Lv&^;+`(!9>cOZ@2f*p~O0Y`P3r;=&E&MnbTKGNb%ci0`
z70}k43STfbfcYk{3RoMWP>2(ILhNIZ{1_O!3{{P+MUea`u%a+aINxsx{_%@W0P@YE
z0R5Lh>28pC2N*|#wJR8k^Vgq39klanx6hW+!eJ-DNH~-T(+tKVU_A_`g4;G_+xdX!
zbV}_6(a!;Aq`cBLJqD&!r9~kMUDm@LxO9UrwqHM>4Pq+
zb}(*)$Q{tHi>&SuV-$b`xeGvmmxbCi*pV&!xGW(b1`Zqr3x6;x72$a=7qv(^{}Pao
zVTVtcciJj*UKqZl(Q~8r)yrFrT)LCUb|J_0LFRg#PqC8nPSL(71g|d{A3QSsAf{MgsNzaG9wcB7zt|o(TUMz
znQSo=EbZ^&zRqUu?`UC5G8faojWYLlFwY-79^dd5R1N|V^0uD+oV4rj2GOfip~1mO
zv#5;3Y?bC6RdsxH!W4EY4GQcQ?43bccUvY8bhPq^E$i%)VPi6Gt5#W18b9<20x#~y^VJNHZ#r>J)5{`<4XHf2uKRfdl@39K>rH?pB;70
zhfM%-2n-&=#R^4^y~ID+_8C$ZCMI<
zA_z$9vtSC(RN>h>B#l4;C^-a@R{)3kRe5B(lzxBw#6}JoS3~gYY;muR)$tzwxMc(P
zbgXAZe|Hd@=X$WNhUE2O1MY@!!WV?;BS`=Zh$=$!I$(}p?o2Hy=aPn9ZHtb~nj>k#
zTj%Jrm-E+7r5gCA9Zo#Ev9k)+qZ
zr>cRA>gU)FsvU{$*|MJN+LqhCgrvXoAOY5oAo=x>au7xgyGMcm$kOvMKstY;AI|Pp
zR>h~Qr*Yi)$+q`)M84fuwWAr+pAabj;5IN--6o7goDsqHlmHa(Q5Avc
z@oBYZvKL1+jiV~-_`mv{ZLe)6>Gq>ec)e$&{N9mF@TV;s_-^Y`JLJL#DR21?X@9|H
zr?pX#PHBA!MX&J-}U+iBl-7mR!q1Ym<-XM0PpweR@R*>ChyMMtCZ0=
zMt(T3A<@T|H@wOtU7P$^h?HM&I+&_25L&FZpK%tdO|c0;2}G_2L*dM>z^@8V{#yO6
zb{tQ3G+Ot0Z*H`j{aoJs27m6@;KM?sYBR}`Mk|U$kqJQL0x&KcfL)XEYbr$fyOa0e
z$g0{R_Uab5fJq3x*}RzhI-0%IAx-2_FyE1eYVnq^?OGJboD*xU3RZ)oX3QSdr_3lW}G`VujQ6at7tdl
zwy=C?LL+BSnn_bh`G^dL%lG=x%KrW4bzIuA#D~Y&xB?Ovj}HI9@Oy$AAu7H9aJP@V
zds!7fYMRgV5|{G3VCT|a+4=^*-?EG$D00hGkP5DcZ~IpXzHM@zV=X}%rK>_%EjU*GT=
zcWqfue+GCF!J1s(IFEN$)rZ8o(br8^(&WswFYw=PuUI%6R$Za#f#R-Ot0J9)+
zHyEmXn31$-IZp=W1uUhBzo+_PVxZ+`Q3!_xj(O8GN-~WqF5}if7*+%7-`lyZH2*
z&$Bk+3dwSt7KQt&sc7Cm4S67zF%5&oCXIRqjp2-(A3_dcUk&Ekh*&!2TvRQXe
zc5mX;HP0jU(uG>V++!pc+a3l1C^;CCcLP(L7`t<6B|mQ3i%(
zLgZT#toPz*U1$NA0kM0*+RJJBLR`LO>fUxrqZ28H<#X#^a@A2aG35sP@gEe&rzpNr
z$iQG**EX$kgJbLi>!LKY%7_b*D6jxTPX>POwEJrboL{>WXV=bh_1G%2>Bk#ax(28V
z_V9|ydvH`yM~oKVq8~h>SNl3Rea#E3b2l>F52g_M!mV3Y0QMAG0G>+0muIHBXekfQ
zI@m^IcA}ShJNVe@XXv#usXp7Dy0C5zpQ)LFdStQP0`y&79a^7gvj{t9fJas8AI>ZWjKsX!TNYOI`JPTPlBF1r6eI$OAK
z{i|$tf%wqf;iksO8`Q6&kTtE(+$$_
zqZ1nWM#HXl`JqfCYtJRki@AHtI>!tqIlE**I=yCwqXj91x$KZ#Jkz_GlU6_Lw
zOqFHwPa*8C{$DnEA+NY8+PM^6C7OwEbi;#Z
zow~PO>|%_<
z`?LkFj;bqK-ry%Lp}LyLcc<*ZhbQ<#PxQx~u~u(!Kqk@aH!inp61Y^TSq0`hf!9Y0
z>k1(NDo%YTprq=I^nvkH_+H~4jtTAx(>-k2qpr2G_AQ^t9n%*eHK?d)n2-1{dGT;p
z8|SZo#ZEMGAmvpmZSvP)zBiwJ6+!@1zw1A#yx+_u$JbGnwT|=r_2xBvb%XP!l8W`8
z&pV44A83d%GZ(?g<=GrolY-J2&D0ffjjvF?RuOb^(fU{I@N*aX3z(lu7ws;B5xWne
z5CU*xsuWLVdf)g4JJ;8V2qplvT)Mu9nA7GJPxvdbhg@AW6oqP
zh2$~7%A#NWeoGAHFcAYHFN2|w*^G4cls);7i}gq9&!x>v9FsE?oBo)RnLn5sIv`g}v4
zd=!3!GpxxAJ>vSd%yMr-N$PT;7ry_ker{z3$)uG4$*G{2l#lt8LX=yd0QHx
zBCxwG&)>kd|DWhIkyERu^X2-T9dZ4)0ycfKC3E7-;o~OpvuS$|i+Ul?=#6bF_}!Lu
z$WIZ3_N|!6xszsb$hb)(^5VXN-mkBbFp;ByakwehDd>uFEMk=_4e6Pxm;Pj7qgP|aGq
zy&@y;4_1pe<2MoL*PYWApctlnQ9kYlZDyQ>YhU7t9@oIn_ksBrAM=_B(K`Ue5ameL
zdPpiS!FMP|J_TSGi2ehZk!eGx@RvDM$QVFfIk$aWDN3|3NT6;TN?op8~L7`dDkqGARr7L8$9N#cn($)vl<`jBxowl{n@kSnAj2~k_7+b9MT
zqcP8bLKWWrUh7gXD9WFJd4@Z$B4>kfgBL$bVkJbQF_Ko2L_BW)>qP1GeqZ`NAfJIN
zv>3-n(T34|KJ%
zF5YX$V@V}DVPX@i!RAIcw5{NpwqM7^@)b+y>vuIUZ3OcOKXuLA1t6=?fUQ%GegwD$$hcCKx_I}D{Tc7#!L{Sh
z+g1*7yYE_7$+c7FI(m@vv4|82dGNAF&^^G3cAB*-d(S(7l9Fwu4z&BPZ(hTrk3Wdz
z@~jeq3R(F^^Z0rDYTKbT(2T27K(!dYJ7rJT6G`0ud_eC;rQnt1>f4%J&Wn*AKQ
z?6Hhc=W>7vF?wO`tb+Lfp6YJr#5bRDWahYvN{%}GJyca@zSBIp=s&Dj^@ii~pO`q6
zODFH<^}Ry-eN9}Z%;z$KUTNQxfkK{ZClX?u0Eq|IVHni*Kj~fAp^MnN!E&rIAU+;>
z*t@A5m-$qD?8%3CbB*N3iLCEK@{3-}Kc6a)Y_c~28D}u3bR2)4d7z`d`LC{4KI7tT
z{NVV>TsnF8f?<8CnxeqCoTzc~G!EMT2ue#cr(>>Ovz-6hyH;j8wyNF+@hTKO*Sm$|
zS1od^75w3eja)U^F^18;0AJR!B;y8^p2L%19^^cqs2pCmI{iMiaRvwOcldU@|G>5@
zRxaVmXB|AEv7>8*tzZvmLjLHi%>C;-~6v}+ZM
z04(Zh=Y-YHCz^JDodQlfqz?gj3d{pt1VFJu0!hnAQyXV;;C_c~CjbLAa-F6+RbA-L
zv^jK8lkN5Te4YRw2X1y%fo2@whyu}(OmNil$7nZ)CKkzy{KeWioIYtr!Ayrw_O$bH
zmj$Cs+7X8y%lN9HS03s9Pe1!8%U3$XXMTKQ6PHbv^RzIuI^JW?CllhERLn~ncHyMz
zX^w;WMf>Ws3gmbvQf=YGTm(RPqJMSzU3@(Jkatj3ITS}h{Cwi+f3xb%WiHIJu7%_m
zyttW90Z=BGV9+xnLHM}@-?@1WSG6o1f&-+Q0jl>UOHwcj?ibzqgf78vTlT1%&dcnX
zyRi5C11TMF36?2z^_rDD^~@uf1BH2zfuYcsV3|FC*tVQoHm%-HjnS|at0d>#l$dj2
z5+9dfIiwu(z-2J=x&+Uey*qo&-Jg<@ZNSDdDrn(9{p@2T-JOxXAX@S%06xs1`%>=`
z9;{kl3WdIT<4Ruc>#z&lN%c9cW=3A2Pn^W$gw=~!_h#URh@&^a)tZZE@VN
zaRt}3x|%`n2J^!%^HH+YA}|&>wTVQ;&&lkz%X}u))Y9AA!?G2NS-E;C{Vx2JYXhQ(
zLjTKN{LG^O@OFp_9pEiT*ud|Qa)d2lmP-wQfrD3kz3jsI!F=#{GW>S)T01#kc$wbh
zuTS-JNM#MzO&K0XnBL!;Od^#?i95n)^`ev`Of}hDvmSp2=4agdvcn**aVwMMH5P~8
ziBp(j{xJ_SJafBy(%1nRLvA)5AL-Y~`TXTw+(zK{-v3klB8
z_%9@+etU*NXkhsifLe$=4o1ehq|$pYo4gwz9PbQ=nC%D3=X3I!3LNUe&K6&aklR>P
z!{FAb3)r(fTirkVnJSv~g}x5{)!9aum0-W}i8io+pOj6-W-VOz5|6vXBz_P~#rS(s
zHAH^}ygP*TxE9QJ0nQGaL8Hm107N131Q>R#$6%5v^MblL_J!7!atg=r;(d0%wQrVHf+-nHO^5n~VzzW4LScI%IAZ>29@eMKzZ+?23|&+$fb0
z^PC$%mJ+k(0+)~9R=2pfPs#!KY*!_(^W2$*%_jb1CA9iZkf6lN9D^N?zJj*kl&^Lyl3ST
zycu`i{TEbB;I8S8Wt{Eg`H!q##B$dH88%d*&>)-(WcX6mi%S4@hR8i&4Axb2`~D@E
z=ed}n>LIEV;Ap3{_JQ$}`Cj9mBPJHcugnyaN~Yak8C*^zN0{<}-Jt4ylt>a@%$Z=y
zvzsFol>kJ(lq!9b@y^f==AkZLKNZRQPP#~5#yf}N`V|>Etz_VZKmW@o|8K}~4TSW#
z@_#?Q#vRH~5R*Tfx;Im|`v%(vAgk67+Lm);+e)`+*wqkyC-lFT`_PL@0HU|2psJn7
z@(6s&y-aHE3@w5ld>#Gaj#CR)$t%~iE_bxtX!k$0X1X1k;#6eNXZ(+nU{od2GYZpt
zmPI81v8%yy%dr0^m@13oM6)3JAh1)W23i*%@hFt-&9IT*A|EHx%6JcFt$BgPEO
z{$AfWmxEk@dS&;Q_g8lb7dj10LE(fz#U%jmN<&yUnN9n_)Z%u+%8Ni$2mne_$Vf_%
zQKc7oO~)ua4*cJS*H9vlo8?D{6xTP-bA+kaXna_IHiN0i#x2gH4{`(+l>nf`g@?eH
z<)Q`&kMUaP-)$?BP65ZHu8Zn-<_xds(D2sAsP0KR$OE0toW0gPGFT3;KTO}x6`@+$
zV#ls{+zSf+M_@V@Lk!OB;t~KsSnFt0?1d8v2uU&BPDNy65PJ+P$I2g#u`=$Rd7z#9
z8wv&4a6)-u@pPh?pL5s0#LE62$3clbXLvZil9pvx=~2|H3zZq}nE(J8`AI}URBZv{
zI7kG-=6B~-aS4C{u?xVu%%yXphI=yI>pO#;CE-junnxybVPsz|wu
z=U0qbo>qMqXTv4uXNpe%bbq}Nj4NFx+70I00hdU?*dbv3US+bJ`ccNzt<&~lstYhv
zcFZpPqAZjE3pPg9o-13IzLn_6AdcNKb#4Pwz^B`|vAu33lc*ePf=+a#&(x$UND8KJjNCaORpo)s78|o@
zb29GQSgDRzPTqs##ye@UsmGt~*=%EndSD>6fhh#tmhit(aSDJm7r`C60UqfTkfp~h
zA$kvp%3|PHBC#S8;ijg&MpO;4@E3u1k8(_D=U4IStqJZqJtegcqvnFS<}WleGwvf^MM#(6vubAA#W1vcjZumN(k+`4eO%SLlzTeX<5j9y
z3r)dpw@qI_y$AH+>wTS^x%NdHD$#}d!1@X#uL~#cC)1mXR{$aqIVT+*>XfgU0PAW<
zYKM1%#tLd7p#93A-;K#R3GJd
zXMOSYdcn5e!xaFhPnDejyw#h17xCrY^0dRVm7k|7a)C7@x!PwWip(U6M)rxY35`fsTU`YsESGPhX
z1l)A!xJko`u;#^>AirzD%ZA@-_VZ9@t6dyig~5Z8LYt4RtmE5Lc4wSJ`PqSes@}oE
zbuaR4kLP7tHvgYO^6Xqt*n-W+O#ti-#iI(PzqjQo06r`^)>23+XLy6hkc1|!6Xj|*
zRY-kIiIwrqhFv+jvMwhf2(~Z#W2m22dari%Rq-B{_IL4GUnej3cJg9xho>jkseke0
zwCWjrth&ip<;+$gT(>Odej!{t*ii?l4S!3Q9Q8C0`I*1JHY4K%u-7BR9t6t?4D}u`
zKk9Sm5bn^GV13?=N%}BTODedgaZmR3xZwF?cKEGSgswMfqJOs}y^7IPiHeoxv-1b4^Wr!RP{jY}4CAS_&
zjsPe;;=O>>mx0(n;_RF0B`&NyWA~#K6(n=q(A}?
zo*}~)f|I}!c2EM0MG*Nh#GeX0ci0yqw}RnWI$ew4qPjV}cYMRJdW)q-{B+YQJAPha
z$@wIif{&@H=lF?@c4e1n#<20BBT#nO<*iH7JHO|dBX6%_eQp5UBkb}r*#Zebh+~&5
zsVIVAg|z}Ur_&=nIh|n)>TBDib3mb`F7{J;Elx_XO?u!psx~Pd-TnEY7W+
zZJRgUidYOD!yElwEL`_8OZ&R~RJD=4|6)jfAL!5Le4P6pc><6RJ{<+oJAfuXoqwig
zhOG`L-aXgby`T3DY;$kN2EMxCHQ%;+zx`_-!tCF*w9@v298=}FA8gmP5@R0eZnHzB
zHza%&Y|(CiGnik|UES|E!*4ex0OF}ChA8GlL06N#n5^KrqpNB;chYP-{w04j^j!E$
z#|B%s_VA?o9+K3Wol7e18p4N-o5cPV6R8U51=j^zCRIg?DtOK(slKl~{Hp6PjO1Sc
za#7<8y-{YvP(ehzCtYuBoL2{mcbOb3)VS@0}3+Sy~N+T-~v#%gvys*ZpRII
zhBj<{RC@lcU|!%3Y?aGW3;OJs01W1s)S>sJ!mOPu&J27=QZm1M0-u~X6``NyyD*(R
zx_{WRjt4qh?Ra`w0#qlgEE2IVtP>(Jdkpo_G8$r~2n}y~X$8|`<*pf8ewL0r3Bm&U
zRr?xrAMv^Ct7}<%u7ji;kay36IWD2c6W&U#tm9!@Pk@>ijR@GbdQkk
z22R!?E&2{KLARx-%2b^&sbMi8D;W{HW|(&WFjX@ZKXia~0VIXh
z9+l#5)fR;ZgcF`J}=q%I$i+$5hs_
zPx%DOBUu(F_Zndye%Hc(Gu~r6v;Nl6!ZW>_J#u%43_?fK!(dzniGLN|!|RtRV*=pL
zsnp3(o+`+IN^+{OvTxGklcFW;T|Uu9N>IwB)Rkf;Ioa;myoUQbn^_m{3!>+5ot9AC
z6xDfoe$yGl;NlJ`09jjf=Rs6L@HUW81Sf%Uol-rGL&i;_E>=oaB)Uyc&w5zF$1(5;
z1+6J5M#Z;(w|T7{xf$ScJFqKVP1b>RDI^7Q94x^gw`pYgbV>jWD0c`Y$Y(fohA$r0
zA_%LWp=_;&i13O|Pg{wy+RkNn-?w5S^UEf%XZd*MmQ|s|{P0c(_uvYdP!+bI(25s&
zJM6@FAvNR>FDhH;T@d+!2TDhl3pHPljg%80e+xPr&PtgV=+-5GLa?(Yd1B>xt&YU(
z%&LPcYB*?IwH=b+R75(Pm&szYHr~ggo^~GZZs&!*E%xPhi>Zq9IV^j7FqVJ`&Ibfw
z;F0NyX$`~`~Omi0P+zC$g0Kcw|xXF`nzq_=9Ruqd%=fDdYz!^e+LP{(rgN`?*eu;
zCIAl1_PwpB;WUVR1dLAr3sSK~`5`&HckzWd7v&yaNQ`d0yLU>N`Wpd%>CjiT?nP1dLBtQcWX7-kV0AaE_Vw
zEn9h=b2gpJw5d=A#qO&_X;7ztHi{2HWD;0UfvHkfxdk=lx_zLQc#F(%})VU_tp|#*Qff3K%#VtW$vxC=@<3b1gx%e-DzvD9t8jT`(+1
z0`CAr?~^bT3D}V5a#Ez`BdLOeN$u1xXXnYaL5>yt_C6pJTCVOAPl9zDB=3~t*>hx}
zHi&!{1gz$0*3O^;1wX>GG8N`N&x`||1U6!{>INw5!)t6>`2Pk|C}k3eY)Ca(B@71v
zX96njQLMBEte-$q`MJ{d+p`3wt%S%Iz!1cVaxA}#
z3+5rYj>l_}<=VfW=M5;G2j(^D1sHTp8`J4~S{OM*GumZbAM!KcVQTXmFwe-roLK=S
zb0K*JaI}wwKX{t$=^SC5x?TC_LF583CO15s7&
zIT5JOfzR?K`~u8x0U=g|95NzdL|q2PMJ}4aufcS!GUR8T!zlm))soj%%nX+3>O8X!
z;88GT|8}WUmZ<}G0jRH=%t8AfNg@%aySs~yt}S$RbW@kEsnl|@TJ>p-eimr(XvPBdHs5QXURz>l0Sx;f2D7q~mX
zRONbEM|@nA-34HBLlZ|Hs_1Kfl(dpW0)Y6gSoH=^E|Qhkk^G(jQW+E}`~FnfTJKzH
zKQkI6Zi+3s3(SS8)#2y)fNds2RD~3UDCk6|f+;MjoG4rY@Ujl&fHU!#c~1J=(yVAbuOG`M-?<2kQ(C+_QLwlVCp
zjE?C5V@b+vnp9qKhNSNiD*!RD$No7$HJnxcF|ewm5=s};&EY@Y8|}zbpI&R>2i*)w
z`D;S-6+}J7_UbZTO(wI^Xg{XMKR(Q
zwgCKS(@IwLE8o^X-qxVmYy#spFz$f3oN0=ciKiElqZKHq3gk7v!M_JM0d&t1_*gAO
zj)ll+U@6Qa@S@ikl%ss)xLPi(-x*bt(FXIc&NjZ=y3_{l^}%IbjL%7jUxWdPr()_)
zfw7kh52fCo6qLuA+86i)h+GNA7u`OV9s5vP$->&%yk~sFc1CJ_4225+Zu2_s?O1P@
z^UQ$+;U-2u4%X9Yhl`_5Sp)*0Dmpi%g8MA(^s<5b0u0NpE%VPRs!KzJ-P@4Kym&0lXu*A9&{$&
zs8Zzl^*eEFWj)m{D0=N?+^#=-=jJtbK%WbVhd%-HtQ^0EPPO~N*w48isj>-W0qXR2
zOd9t)jzXOP)eyTsy>mH^O+dG|Ox+vR2lh+48{E8UB}@9cY$)=4QGM-X4nFWG+oTzd
z#E^N@@Ci!mG^S)SiJ3G>nkFk(FXg!xJt|4%Fgc-6D6!{VRrNNusIZ9qkdW@avf)+!
z*3oRkytoU2c|^(#ofXOUe>TXxi9Ga~Xe)S^avXvN{AKL05CUMNoiNv@gWNI>LXjd3gn8rR9{CmQqquMl4oB
zEEXdcjbWq(V`~PPWhF^k7D+QfLVpR9c)XAP{yzHS{q)L*(AQ08X9qpKzJaWKjd|<-
zIK8C81_3y3d;{~!#`{K--ujYFW}FMwzv2f{UEWz0&(0?KW-o}`3C7Sm6WcCvy04uA
z=DnF25d7}yLjZzzI%se;5WO`W+?H|NilG;}^XWA+v&Q+?)}RmdbWgkQ8oIt$kwZhB
z01@R5D+$K_i`Yt~z4*6s)>3$-Wjn@Fe@V3S>o4CV_2$&-X}o{JWTwW-vw~RMI!4{S
z&RP4CeFt+P#V5%=FI6`PRiu%?b+2(*lg7Uxaa@?87(N6b$UHK&Eja*^O38E7Yozc0
zbn4!|k7chBNf;jPYU9@S)x6T%X$wXWtC3g##rnHGrjl}$p{9BXpQ@f_>-b@i_;*M9
z8oN}hhpH)jQC0ZyixWR7jq&7_!rBMMJ0bD!Fy^zhkDLU+-`Nnm7A(iQvI2hn+0?yk
zGzNd<7qK7vL%rC$#V#=_yiC>L@@e1pH^bEm06qp&2GKQc9zE%$
z*EP-!6)JG>xRjc>DH&&Ve-HO`Y~cQmW;-``2>Fv}AJZ;O6PYqqAME&vO?IUi;T(mT
zu%Brb_jj~#_PQ6{CMYxI@nGKV!G)<16(pl7h888s@@1beR@ebM_1MJzN!Wr
z<*tyV`^<4Go@BB6UEp>D^JE9ZsRG@UcOg&jEFWXnakCIig<<>f%lYVWK$GkQ!1p_B
zuqwrhWF#HgKWv&uZ8ZGj+RqTh&9+3pjm+>`eQM#>ypoJf`
z7ru&Q7?c~))L+Ca4LA6>3&|@1e0`&NF?VfIYH}E~-RSlID9l>ueyMPWJf3)Y6aZCo
zK^K>dx}IWw?wGc(9s5&o$|FMyK9^y5piek^_KuGGyJzf=tQw<<3b_*(j&DO}b<3tzcF1%v%b6Q@tku9}kW{Wn!3uNdQvg1h
zPI=7e0T#aIpJyI8tk#UeEufM9rlAY>_VzdVcFP+)BE3gk=0
zItr%lWE$px#Flfixntxq9yev)u7*kh@XN5S7#9IIqz?RGy1yD?rD^39@CJv3Zv4pg_
z1Qvjh-W&Gk7!!c7Il6=N7gzv_lrLifP^2HG*oQSH0G?kcpc%ACk4VpHZf!8q$M1Wh
zsfJ?$;K{In0^r%-Sc78XK(+!B(K+Z>n
zzYfL(Amjy?yN@;G0p+Q?ybC~{#yz;(F##wLqet6o#st9gi;i7_Jy|q@8;l8n=f@i$
z0Jb`|y>j3u9NP00V-1Fl1NjpGf4X~E6E9*FXh+ee!}^qk?I(W%P}q4Cu>cg75o2!)
z#(^;b@Z{In9l(=CBe=ns0C;{pcLA7LQo+xr?UP+$E6;b2HOMyy?(NvX^=&I$a)OI6
z96H1GV%St50YIr#&lrtN4s=^4TTDTMIZ+V`K!%|Xn*fZ=&ptM4{~X9s019Lb8npsY
zApZGZ52LN6CxdeofIRYR;LrmKfak0S3_E&qs;p;bSd?7m7i1K$63u_&-WAP}yC{3{I*2n_rS24SKDe@=ZSuYo@(?i#P9K@}rZ
z+aM4Or0`1Wt&j0;CdOAot*ZwK9&fK&2Fu#*Ifr-^X)ubk0^R9y>2ujM7&Y4W5BFW~
zCElpNV*Uny{~iV&CnnhD6AL2%Z}&S56wK_oZ{BBndxg-`&oOT+UM#zh+(f+51S`TYAy^0@QbDw~JJ=-U*7Sxh^;lbY!tsId;SP-c!I7t`p#(nSeY;F5()R=?mhv`yFmb
z+OUUh)ZL4SV(5#b@#p83ro2R9Am9X3fFKUilbLPL2*8^P$WvB|BWY9gFe|tK>f$y6
z3`>?Zf1{HGC$yoC3Y`EGKrpoM_6-UucZ_~oYj0Vm*w!}VoBcDy(K?2zQmXL#~=d-cqL$VzQ&)g5HY=?C7ZyexSl$9uyL)OT=Jc!XsehkU4eCd1hH0(8B4h
zR=7UFYUNQz0kRa`!EByM%)QhG5%qx&jmh3;mH^X#%wsR
z3(Z%89lIX}WmG6;8FEl!tbh;5KZs$ljwZPN<4l5cKzKoiXtiG7AmU5}_s_zDibdlY
zVa@10p%;)}_s~9ZEIx)D$T%zqMUvw1RD&T_6lF62>D+HpsN*0dxs1~W7R9S*BQMes
zR+NO&s{w{tOWh_>gi(WkBCc4eQQEy&!0{ip^f1M+e$Vi+>@x`@cj5
zZ0;eF7NkC{>p$ed{1M?uF-UbJAEg<>N^S{CA0i0>h0rGyXi`k(LpcjM@J;Y?B{oh8
zctix}I1bSdB^L;#l&qG1FDFTILTx|o>5^I5iwfx`aL6Zw5SHK*LENn;uoVqgS$^~^tbL_o!?KwdD4jrP
zA_ZLc)Sxdy_MW^5>oY?J;$8}mu_toRw&RCr;v7F31s8|mcB
z5~mI3$58V4>p80k!-l?TZU&T
zVUx4h58?bEE*KUY5M!peV;Mi&l<~_kURBsHamrTg;oWSg0EF8ZFXBRd8qCyQt1Qu{`nW@%qDuyxK=l)U-0+>1>
zOu^{VHl_Z@eWiBv&5Ai^95>lmBQpwHR5
zcaZF2RIh+<3X7W2f4r6&jAS;hOkoZ=g$CT0W=S%a++u?~h;*(6boZIx!reh(c<`^c
z?VBn8j+zB#iRI`A(O0|tX)`Vsf{@>sw=8}#JaeIse5eEo^`zwZPCWBM=M?C|u1h8C
z6AS@P;*K_??&Czk!18wVS7DJj$9T`Ac47CCs0c$mY|Ou0D&$6LLGITtlJ_e$1V2(F
zw<5f-@U=oO3_jdFv4M_Po$4{k*FI4;0V@1PTak_x^c1u?)-dG41YX#$5m+#|FYZR%
z5d5e_sgyb`pVq_@GBf+oH;(8cpuqBg@P`PH{Wy?%`<_-PDYpPp6Ak2
zzD9MMHfB44tZnP^lZazfGt~Ytd7=kYbyKY5CuODl7Fc4~2I#}M24Xv3xE;mN*F&jQ54g2uFk`80!U
zIu#``+t5cm$3$gZU!X8l@c~vjjpmevK}N_Cl_B>9uWI#}ls{CcO5LiZ66LbqAN;s{
zVE4w_BKF!MW|tHXL(3_#+?C)Sb&2o_k0pv+^{mr?L5U`%_OYf%^vmKlXHU_J50{
z)2(bKx9vTSR_&CQG4O^2
zAWIq2*JQ-MD|4FmVs2YM1jRjniS_Bl?BB>mXMRGQ$vWPey6?~KF9@%^>&{mA*l=I9
zw~2@~(zF@C&M|Vj53a`U6D7w098FM14sDWHaOl~`i=%DKT=xd=`|0n?NeSGYbT&nYS_F%lQ%80R1w`!`&fEVaw$u-WdWO5|lW|iMY-jXCD!`vQLpDSSi
zC-IcKqH0o1pQx|O?JOiyyB*3L5;nzuuv5p2R*B4q%rnH^tk~>5A4Bg4>LSX1cF6C3
zBeKQmnq~2CMMQK_gHQDq!{cO){{B0&L;BHV^E+DEt@;)4JD9rtMAw!}cY~@>`v}PF
z(&TuChNlts5j}=b|0~L;Rw!YWjw%h(xhUHlT3isah&*
z)+irdZ%nnRH>N4zQrh%?9FACyL>l6-P1IOWjC*L#K*u3piBjxAu|yL#{seKM`rhjP
zKOHVCluY$n1c4>8wCZh3uJ
z##)=Sj~;q9l@+QCv_9CV>PFx{oRLcS;Iklowo+yO{XLWMBqEWszeUkvTV-lj+M8-4
z!~wHaA!GuTYwId3dU5qiXt|?sCD?VAmg&n12)8oqU&kcR5KBx8$5YhmUih;;eEp8=
zP*;}zxtqXGBJKgbSs9bfWNyl8!@*!K(E>h)weKn{zP={|4vPXvsV$lh%OH93clDJ7
zW0qkKoy_l#$}{(f-3K2qUpYC6G0NIorLl7Qeh=18>BuOPxDH8YBa1mY`{(2K)6ICP
z?bF8YJP#-NqI-x?2x=)Fa|rKaN-`p-S_41;C9Bz>_4!AD<^HO7KV$0q^ZODL=M97!
zdHmENa0efYJ3=dgslaWQDq2QL#Lbgm`&i;;XK$eNs*;^NMAk{GY?&u0LReEmTc}Ir
zGQF1*_sp68|Mu^4)TUK$(-*6U6^HPEG`+dkLDSVbH(6~YP?t%FHvqZ)V9Hv
zjDfRFndoY8(f2F=P-~gwEoYzlckV|QMs*#7nUT9ULR
zNPHl&_{}6Fb=b4Pck64!SEEc|>QA`njGB(Ro7YkR@sbA)xNQ~6XaI{#K!@hq*)}9K
z4zoV%G=`fI
z40?_B<+Z}>pSGHyBb~dnDkhuS1m9r#1*EgiXbc$>R*oZ>9dcoUa;jqVKRt&Z6gU--j8=J$&_oWz
z&6lx%GCh|2eK}39Qman^`npaoS%3JQ;94fO=6X+OJSp9O_kG5ujwZO6(B|~rt)mfc
zux{(u7F}K~a>^MMh|c@{B>rDon$WKRX@oP!cTGU0)(Vk#ye}%wJ`%ecT}Wk2H+#P^
z)R15$IBew(Bc^WgAAX*-ScM}bML=Kx+K0l4Nj26d+wV1*o6UB|mnw
z808R6Z?m%`07m9n{%Yim$>KUNmVnXmlpzG4QowC+U@lK3npsHDWTjkQ2lk0m*M9v{+zv~K1yJ{6amq~_%fXwTIWH%iXjY1u8c(Sdn>D}M>eM}m
zGQq+lf?C{ECGBv0BtmD|e8m}rZ$XbGT{0oAg(ch{e)BT+_mXEjo$TVYy>6RLOd263
zRtTRHPh(leU{tytYV0ky2jwegJS@sHx<_Uedv;<{#qrA+rN!FqMX$$h7l+?oT4#R%
zH`L@t^#i;Ox5;gDv7jW$9}s%iG0#D3F6I;2hB<9!9g!y9is;XUoiol
z*CM`$&e0iM7bMAO{Y>p<+WDX?{qlYLvvc$@KB5Yx=H->MWon_7N&vuSJ~jhNJBjt+
zbRA2#7x*FmTo9%&W{hSwEBAo85fanrelHh^76l9judkhdig>JbboguNz1a=kEIzlf
zzoXL%2KyQXSu4i=mZy?epGNTL=#YSCd#rEc-NXA-kDy_U+2)f0%-m54(GMRBC
zNJ+v!Y_$it@ujMyD%7xfcNJ%v=+G%_wla{m$?I#%V^H4LU7&0lnO3b-C^B~5-0b&v
zbQ|67>)&XC$?3+B%}0SK9L@vsd?pH%0YblM`ksRJ1f3rN$Zjz^`s?Z2-s?T9hxhMj
zN@k-Eu=)_s;${
zBKn3)+Wo3fEc|8c9wfgehnVEkbv8#1&5uZ_!F&hnqI-$%fU
z-5#k)jCC_#2{42AipZtw`T*-W<+Z+?(FUr;JGfo;&D8$!BXCtvt_x-w_%S2B)u_;9
zmA{QlT67EfDT#Kc!24O&Lg|X+!!7A2fZlSnUN6gMjOTi-SU*
zo6RAiCq8(Gdu^FRBPwEEf24;uv+o*Xq7f7!-`T!oG+GEp8SIpwRiplCM2Ml=oVS|q
zap+(=JP6Ddf?ztZd79TLCZeyyWYod57}3o
z__GGv4w7zpcg~yO5%oobU=qf#Q?T{RH-BdC7$xo-FP6Vd2w$9+D($_7T8N=25PSR{
zkGU*z%^Nj8s)(2s6ZbAzTIUSe#RR$efafDfQ8xLI1f3E3K#$hRUJ@!W$M0;J3`&$7
zA(#cg>;**Geipg;RUhU-|B8VQ&}RQv>fwDxa3$t5^YxA5E1B^tMb%)otprRq$2>@b
zT8{XJTYn9?a*@d<({mwmPD0&%>%w^*r^i5DJ=w{?72QvvJYM6NyTfB1mKS^089`nZ
zblf_tieUU%B=W`hb5ydj^}Eo@^wZu@{~{+WUg0EP<^mK+xk54HC1=q$JCT$%8xJ?)
zt?mJZSwS9!fiFVZV6ZEc5rgArJQM;~Ld*V$_W4n~`pQK>ZkF8GsACa~U$_52%icG4
zFLHyknXlS@yTu-JGgf#PkP$FPWKdpZ6t0>*N`L*=_9^7?HzaUm++#g)fwc
z#2iU%fTn#ZA=cWEd2zuoH#)Uc(o|rO^pH16lO&l%s5@ymy3MDlx7l#{3g*G|s`8|m
zXOA%wNHXMs1u6W5hO;NeNo{wMrDI!-X%2sWK9cY<9+4PIzm+fBqr;xKQJtK0Ind}W!~PD?_<#hz9X$pSH+uZy=&4wlBb3T$v1
zL@Z(JI<_V{Qf2eNZ3RJ>*^nCJeNwp$`7X!|Wut
zCY5Flt#gVdHCcB6Y;&F}m+C+^)EkK;K4*Hu{-QLW-XZW`Xr=kgNTOs7MBH(HNT8%}
z9NFA&oThl=jyNmtk$;J|!M0EeRGW(U063pkfN
zXHEg+I0?a$uH(ASJxAxTAl(VyQfcs4zuTqDSwZw{4*3#ZM!3$gK}5po+5}B;NYKt>
z=K7=8qml!DDTO1wE8Wxnvj$;xZOiF;gK;axjFuEbtO{huoSjKW
z&C1NZ-LdfjZGXE#MEwp#Apgyyaf-GClyf){&M8O~qRuZOR_^Y+za_S4Mds>A7IVPy
z5Jo02CnP)Bne$bkv`3Hjb%zIR-RksbMrJLWl6~QfvJ}{+mH3J_jTrN*7i$RGr^uY4*NG?0OYsw2{+thA
zZ1&5w)M<}_@pIFxls<~spO;WG5vjPRTm?;EVqe+CoG$dHES-!xm96{H#wP{T4CWHs
zHor4y{JdlJ1jTpt{A`E0hkt(GXKUG#z#kShR#Zv~-J%51xzKf@AE47|h!6;sDxo!A
z#qM}ptul_3r7Gr;)e2E5{(NGEBQ$GA3((q{j|giO&kPN*{&Nk)OP5HnmG_wykhX
z89jx4xjHYK95`(rl8j>gC4dwIq~PFLCrmwZhxpFldTG8QCe&h%pKdg^)-Bj-MM2Bw
zR5w4m`4_0(qcMIsN8c+Kw_i10ua6iy1=9~3e@-A(Qx*B6Et0W=NCI1Wd0lQ?)31+R
z)(CmgBl}1wEhU8lj&qrg8IK2?mn3>S8
z?=P!U5&T*B0^Br(42+(Kbj;Pc;0>mh@6#QiA-W
z1()dWNY;pt*ZF&il5e1oH65TE(JB}8vWM)FUxroRiZnrX?^+<Z%#94RGiQJJR<-XRa&Ao7S!hjNm0J`>o*O2q!r}cQZ`@Eb+I$GzM901@
z;qR}Lo@ogq9-FVNTShS0I^V+P{Gd54kEA`t!?R)`q!{cFqb2#NRK^a)3cNeoe{VVrNqQ
z!}~X1AMl>Gho&+A-(7&82xUau+PuKT#)TZhiB*cJeg*BoVO{suR
zjWlSsKhy7MLHsB5U-<@x(;giL6kr}ML*G6g8B!-Z5H;QLiR3V}pd?R$V9ou^7_VQh
z>CXKy-XK2oni4GS4x%yE`B`2{@0uGZ(pD!D|Les&SGv4AbsPN0t$?6rJDT7I-3;tD
zTIt>Mp_sQ~hY61`_&44PB-bFJh5lm?fY6*l4tkDtad~7a-`Gj(r@F~n=spP_#SFyh
z@AeAY?!jm=`&i4wtlWX^
zL(KTS{yUZ-nmFmKe-fXT#7tM9p9MDc#Vm
zGDdeghtIM?9)Eo36=JDRtfZBrY5RW_t)o1io4!U6W-ZyLs1c-ac4IXqxYj!nq3|Pn
zl~hHVpJ=YZW;$qiS3|tuk6hXCEKvm`H3aO;dzx1={f?9wyJ;Lmgb@8crVShfA9sQWIILtldzTOt{|DDYh>BqmLbE31N$!h-Xs$o=gV&
z^4CRd4fc$~@T3;wbe#Z>~NxsjN@Csi=ssj{#iM
zb@8j4$dP(JCo@**NQNC>{!aAt(5EawUhFME9k8--NaY`d~EX&_Lzc>_2zOzu~>^U(i4$wL}!~>@-6f#>sq@bcPnnfii-cn0!fw0)<@8b%C
zg=krYemc$xS(4ngZE_kMmy`mZ77wB4tIEf0Fke=CcJ3}RUy^1E*xA7wJwpkO;h;Ry
zZFF;zM{)%zBKr0%bPY9R-HqO*%V%w?(ib}rU0tL#X;`H6
zlL9~!O>a;T_J}76HOfU-EU3NQ`D3>uzE_8|)h_R+G9SFp<}!)c*o^2QYLv{t)W!%*
z#-2GM;%U$J+w~m2p~m{L)Bzm`n?tQjF@5g`A}%ow^G}19uh%;YELZ(Fobps_-TMEBkX-&@fq7(q5xR0b_>(c;+=y%2
zVJsHD{e`mJCTdGFiEgg3DT*(U1Xz=Rd4el8U6DWVb;%tSjVvN{B7hbwaIXtXw^hIe
zb#S9bC*Fw-o&HJ;qh`bx?^B#^`*`;6Y9rpZ!02B30MPp{HCM#Sw@a%4TC+_A=z_kt
z-THF}hs$Hf>et34(y;*TsycJ?WurRJ#=`X!_tV_%9t
zOpC`IDE70$0INH6oS68{MP)XQM2)+>D%fi*C39JsmwmMO-vfKCo8JN&7P2O?NpJ=5
zRlxDJV`KPkqL<3+2>StGWvYA5%BvmtKqjOk5FciOP?%7zIJ*
zZS)7zrQE^oN~znJN`HR%^6t**PQRnxqQ%wdNn(Al@AZMn8;AeyMOY<+{2)sd`&|nA
zoS>;^C074XlYs~*(+IMh9}@7fJ*b;fQ>w*qx*(7gtZnxX%g1V$7X>8Hn|NiJVl5aS
z_9U>Y(#H<=t}sT>?y_RDV>E)x9EAqh$ApmO=pYm2Zzqf{McYDR-vLlxcX%DF$vV(<
zbufW!E-;GzcUSuG;(YqOg!XKS4G3D-K0UmH7Sq5Lu-Nobv(%9dHS8jURHTv`)yH71
zMF}X=BQ8uX)3bv2OcM8mkgpBF4!Q%wELQ2sU;aG~qg;ott-~Gv{Y(hjW$jv0oTjn$
zA7E_iTh6)L=yWcoZ@Q5AKpw1V_inKyP88*i@e@xJ6>3)E1D~CSu=fun>edD^#S}^P
zL}}q+?)RZ!{I{QxA2d#0lYWtTBL!5E1|?+6yq;)d^)z~BN|)GjM3vq3<66%&2)T*8
zo8=Ai4QH<$S20jgLXBjna@Oas(9zN*th{9Eih3M<&nY0oRah=NGUs8GU2GV;WnxxL
z`(<~BwCNVc-+#+eLn1F`(bIyKne8yW*{LZ9w9P82ZgkEYEKX#Pc2SZ
z<4@GwT2EkARM$3;NnCJjK1@BvNf20*g5L!QUP@FwNX1pX3T0NwhAL3C;3xElxDyxs
zYJU4a&@6juv3dp0`_O48rkSbC0N0c!vheSf?f`XfOgzMjqi6YKIk!KmenoJbv?8FH
zP2rlm{l5VMq8Q~mH7jZJCdMk}o2s0pk!a6QaLk!gl)BLqk;XFGMw{i}RYR>eP>Xm3
zIiF_gd{tKjw)J}!d^I~gW!MTTyaUO~Cl<_L7P%dY-vdqU7O4&rTQTHjmyUxS3mVn()|YJ`B@cFV2_q`i$vI`>$uO=kn^LTVm0;76KJbEV6I&W1YP>Y
z%1@UWX&HCdFAX2WH3!V~wzc>s=~<%~tbh69$|!aT@b%zlxSzehjz
zp|d;cnLp2`pQ^Xc3gh&_z)*TjC&q3Axnvq7Ut{>9Fk&d1{GFbG8SF&$rgRn!%PVxY
zf`9@&D_?SN+>5mh6nir5NiYwpo+HRY{jG;^3tp<5Fv!9HjnE-{3x10mRtsRHDAusn
zdj0*Y5HMdiSN7tU8cSa>8s80ME$Z@T1a6yAXY$dR{Z)~xx#DYK22iYk%1w&L`iJPH
zRh0K>gtIgu=A9R^=M}4@n#?7e>diJC9FB$^ps{ZIiJ7<;4@u;)qL`O
z;)5q3m?*Wh9DWD(SaB%6^Z+QmVWNbIC(kuOU5{B))bcRIW0IC2lifYfnq8+Xvx
z%babB4Oi}F2T*n`dT9d6-4A?_w!#-->egCA-e7_0pn(ZWN71T?Xn4^rrd23zc%LjX
z)7)&LF@RYDHMdjxw4B3?zWE6YD)EUB=mqFCP2m^qo#GcS{$`fkl`F3UEak-bPCsuR
zBRTP;G02~C>TNX#RDLNUa)wQ+L|~wrb)iAY%vLn%1mH2`D`9pE-cyY-2;&`>gByaI
zcIrx%V$x{PgVS$vym`vVhxQYY%g0sIa>Ab#0#K{N&x{zDAGmo<8QIKD5c%k_NN
zcJ6-tzv?>8&RudhktHM4IdsV^pC@jW_qDA3-tR0RYrcw#d32`{mz`C1;~XAe5#(Kd
zhxQqskDGG(hjjOuOQ)mMOqd0lpm)Q%Pe0-yLx3csG{^GW$FAA&KG0JG83pZ`xDSWb
z7L|6DcO>hm%^f#6=V{+hSC}R%SE)j2Erbd#ji>HUKYPgc)3)0H&i4FQsXZ)%Rbw%)
zx;q&~d$Ab4PpYV4?dLEb{0=hRGW>4;Un{K0pfG
zhknGFyvaM*iS*uj8eG!g7*D_P*OK;GafoKMrtn2{C*jC(znbM%Po;0W6W@vv|Aa;t
z$?AR&TFlODs6XHnDip23An?a94Y%9VYyEx`^TiRA3)j^WD7Nql_ZVktTZQ
zs}EO*ebkGRS8}$sUw+d{rbkmu)8LITMesG}+Xn~Dnt!mJ%iQTF+M+JqY;5InQFbMl
z*y0L%RH1fED!lkhZFyG8QhN$?dF>-#^h&H3<>>Dd={|9M_keb=@U%#D*!Z1{&t_WO
z&n|yx-(NcARGcjR?G0NHk_un`S}u=q-Q#hU&=|1ANTuoJe7g*zFD-5}5Gg+N&$QH!~J?_~XPmIw56GR!sz*Z)e)=EDpL9E&?
zN%ALdhOzb|#(25Q?fbmgI~KcU6=abtk)CM>BO<`%E?|!w<@NX;O)On)J)U9lYg7K8
z2Jc;tc|jj;(M1*TZ6_UQQFwRCK7D_Mj5||mvRuzdtiauob#pjdL6xH`)NSBaiV>4q
zkNp>nE)Z;aNzOKv9Hj+7;mAlzi(cK
zfB)Q^D@_V5`^4`)`3yY4?0LBIQ;!@4g(Q8*oe-Zz6W&AxFyb;9PewvVSPWZqO?JMs^ncsML;fW6Pz
zImohU58@<$YI)2)5A1-AUf}ENJ0#EzIkPeUWL^L2K9~OGc_X=C7$x|mM`SC~C#X)DHRT7s6_g3djG
z)_$Z(+UyX|ei;lfx~eMr!iZG)Zy6aBhj{38#c)enINA+G(k-cR;@Ygw
zt6mG!AuQW^I&*s)N@oGMApW!p;_qr|K}$9F7j)ez`Iwq-US`eG!(M~eZGgCaa@pic
zR5B`qo(56PQ`9w4R1AhF#XWve%Z4MLea
z<7j<2=>q1@VO<#GLR9^ce>SF`GB|fX!|pYBT6;nF;cno)OM3nZ@1dvvb%=&Bw1Ph2
z=zFq{I65d0p=aec&^c57>EQaU;p?nd*8EO?%Uh;@N!TxuzVmrYOkAsE27T?l+hrO>
z0#&KSt}%`xlTj?La1Xo^?NJwumQjx+eEuY|r$;U=?MrW|h-^iF$k1|J#U@v+t!5k5
z0Yzh~lj<`uI_lXx877k`?y2r?pTAy2&vkWANqbA4qGSJfuD5(1xM$EK(fjd@;ah1z
zs)Fk3kPXsR%ftm4&*x1&L{pBH6QVdEtP6;@oIg+OZt#QNXl}-)3@JijN85dK2MvHQ
zzJ=`3touJ={C2+;2Pb6qInEEZbe26VGwwa{L)g!TY|hfkI)XJxBz_B2^Rd?_cZ-ydy|lqT~yUi<1@*f4t;ewsy!
zMIQWQ4~^`7Sju8EG9IprVH_KS)vuM!`MI>-#2GRJ@%D&+Pbcqy$tsnuh7&-Bn4a7;
zK2Er0?AC+pj}3Mv+-83qy)_uP;*hvpm7`V6UhbcYv9Yzay3H3TuD|3*-gnRm*5n0X
z6f?+}nzIh9D_Q5C>LtB53f^gDGRind?Xn1%uPJx1%3QLz?fn;wq!=z8->A*MGeXrZ
zHl5Cu;lkMU7F-(iXAG~+th19M`2jV`z
z+>if3To>H`zYX~Y*L(c$j+2(*>I6-A9To0a_u#iLRCP#H7MgHrGrakS7k(GYxL&MaTYbPKpP2xvEG43~po
zxgy;{)IKkB3lw<$8Xj!qh&>hme)^R*koLf!G+`2oNA&j!C9i%<>D}yV`Qw7(U`k-d
zFOrV})YUhAm!%IL%F~PwH`ld&G$<2{60aVLCIdnJE_X|5;ft+u8lUs6d*~k2Uz+dbtasP4&TD`;S_AB7vaBtw)wHUEK$mbaFNIHbD+|2
z01jvb<=%;6Hwv|ff}@#*#krR~txr^jqz#W{uP2-3oLlTPW2!ZH?_H4NGfz8P1*T|`
zow~4Ts)bMy|9xfgG+S{QGw;IKkD(LFj+xg)mi|BS7K2kf&*(^1bGnosdL^UZbr{#v
z20oYV>GfTX$G^%6$}}@_ruZdCL5lXg6XSqlqQ%mv#ck585O(7{UcXJNW;N25x;@~q
z>32Z)L2r~bMH~fNrjcda5L5JYN_>84t46btJ~VAR&wGMW;P`QJ=hA1<2}pl_vH>FM
z=0g|Cm`l1h%xX;4+uZ837vx<09ov0j{Tc{tj=Rw0qAT-H6X>)So?`RAEc6`QXuk=nn7pkOiE9~O0_Zo3hTjU2`g~w6o
zWUQ$p{%7_?ov^NCCTf55?(Xl<2B*%sO9f~3THZUI(S`HVV|X>rp6STw`Rn+0wrH-)
z0@WAt!W%Of`_@!_B!l7`!Racrme|ALTW?lJ2=AXt%b{ZTSGx`zSu=V3W&C~Ab~U0@
zreFK~_l829yQTNH?f&_a2hjl3|2RGa7dDhHFnH|#l>c^Mf54LVj_>9IH`BBfXXGm
zQ=Xq1l=#!F(i2=((nW1LKB~>u>oc6ZzvSn3*~RqK({rPcmir$MB_FAwFIpXI&CG(>R(Tz?LwYV>)ofq}?B+geWli6=agPd}Kf
zbFvI6PPCi;Z`fAjQ8!gqn5uPCRvb-4s>Zwj3ma9g)*qvmGODsNv%A(d>&5(tqja+_ZjTgYF^{aOU!%{>dGKoDlGNm?7qEBhT}
zXzY+N<6eGu!q+L)o=%G!krMNmOFi?)ivi=ssI-3z&R<_eb+oexxh%#h82%2L&?>d}
zJuzA>W*ajE?bW7`68)Pw8D^Luw9k0OLfI@E)by7pi#vPPPQ~DF8ogYKyrwk~>>skn
zVGLpRdA`>{y=#X7`zz;_qq%qxGx0LM{?O}W1D~`Q6vP?9^_T6rnxmHeZ{KN;yeOC9dR(_??hTC*9}e2=|S^AEziQS~uoF1#l=c#!n)77Cm3tEx&%i8$^(T}9r
z!>L`L)e0D0W#6e}Z%ce?|5QeArZg17YRiBY1SmP9O^vG<)C3$97ULbQ0gN1u`nDPx
zb#{n7XMN&~Zn*jqduFTK(33(6&$FN8M5!Xd$?C(}T;7Q^n^gu5rG#l2hK2Nsn~GD^sVFMzaFYRc2ex?0wFL$&}?C
zORMn=MY=5;wjLWcHoP+}t2!9}sX4>&KB_&Lduy(P$Hn)+pZ|u$Y|OHl?BT84;h19<9&&TfTTn{sl4qBnELJ{j@J(
z`H0@}Gom4Da0Ut&HnSXEC(6M!eMHKe2|Vggg7;e!%(cR{rk~0uCkYbiNuDIPOiP
zV^yo~on`L_kM@F)=?L@f_Y3FO!DqDH%cRpv>uiYPC?+0(VGjgGqQ|=P-FbiSUwme@
z`h4Z2$zuMpZ)(AK^6ot216qT=H+MR~BzDjTpKY(lgxAqSLo;{**aERYvcZQ9sjVlK
zE$u?LHO3o)%BWaYrnrk4RZ(Xb?r1yrXdoBk`A!I8icND6OiwpUHDpSa!}pt;i5yu<
zDxf$|0b^-k>tgzQ6ERH9ee|5x^7iYuq%L%M4|mpqj*}IYq5}VyP6zxB9OqP7`dyjB
z=>muI3mp2)5PI|G27eNrB%SZvWo_*R{ZX!}!*zA>ult)Hg3nh{{=T`X-_}G4v^%p9
zcS@Y_l-omf!RIWQeaVlLG0M8t!*H==FA;&G=(zrFuZ6E~nR4cfzT0Rk&O3Zv!Ut)7
zd{lT}9J1YxqD+*nUo(L%;bbd?_2w&@LI?EeaxUG4&S%ab>8@$P&;pkFr}>PbYza(H
zuAXB>5}DYWv|BOIDhGu26rdY84YnWm;d(56LPO7AAI@H{wzI7QN$G6?tg<)P;}t
ze|G_>`s1P)tHxZMu#pTd!v!<;p2Co5haU>%R$gGu7bHRqm}&)_W5*;)e>d;H(^rYP
zua`Ako%K3Wjr;`VnDYMmOgrC}cj2P?Z*gJy4kSPDTSS`!+5cs5KJaA{6K3)DeWnvC
z^Y|A-=FzAxemD!l6ZK`3bmvc1DrrR23Z^F)`^rYGID`Kb{(5(A7nT@77a({Eqe7h;
z*FewiCY$axbTSr7C*z-f&d@W@b44iL?C$KeC-gbKDwFA9H1H|k!&2f3{k{d&14zes
z#(;B^10hQq))~9|yPeE`j|;v3cQuc_O_9v_J$p%(wdI^+R0o-)C~lSK;7<_T2*E|8
zxAeH{=*j12;Tf60;f$KKQ1^aDpz@uu`yCl&%vz*z)^qxWmEuf)`zZ-j+HAEne%Ir7
zC+l8~oBA^~ZuII37stlLBuNAx1$lY-t9(!7lBcHM7_^2J)$2r?_|adCIQyp;YulJ&
z)o4ra$S*cpski04t-Y9ntElSsEoYRrXgPLny#GVQ&n4=Wc|z(WZKj|x`vBP|Q80>g
zF$c7g=CJg_-&F>__!iEpzB4q$BqY5n1AP4aRjw0$$!wKo-6fXtXDT<0^>*J@YFE^5
zNtBlL)LcH(rgZ?D{{Uf6eDbwMKr(AuIFgQhTVh5;43OolC@Y`@7FPPGh~3@E?r+LY
zvxu5xx#?d07uaNDjX59GyN$NCR;{%=nAULNz6p~%zaOlP1uAqgqaA8ju2y|pGAZ#I
z#@(6PIO=BS0^7crCxhXVzZbpO*COl`o!@u(oy{4&Y^qNwnB^Ph!}Mc}d53N$D)N75
zy6S)^x@f<8XZa=C!f9Lw&HXs^+icg+e20Lyfz#!
zAg?!I(B_mYV*Y{byLKJt(oQ$~C=NWZ=lM`s{<$D1;cEdY^RpAcl>tb)%{BOV%V=61
zCak~roH8x$0~&~PgqWgAb_??i$666(gabrtND@*pSGF@*pi?}Qjdh&t6)CC)vR*u5
z*xc99lodkO-E1qiql2nThHO#`zSF$*^@Xkrv;?i7EvmJkZu!iuNB1&_x4O?<90r$<
zIvg}+uQp~;*-O{)^pAHE)>So`DVZ{{K(%`L4c%8Du>y=8c*kuz2g42EcjqW@3o?wiax*%Lmo6S*{;A4ZDgqAsuTAr;N
z%>xKd*OG$RHAHvegoQ-1XYR4!G_gYMDX{1=##8BfKF?Fx3`cH~9GaY456SrCVwUZ#
zNGJyT8yBCVmDpJ+mE=SxH&?8qTYxNUshY*iDSK_iYGB6AK9ly<6Ku^qKGsFg$4U`8
z{)|x2RI~Z(yKP$#;UjNYkdt3;&WUt}WRzHXs&*TQC?QtMnlpXY>G=;(({edGizmSR
z-W{$wi905&ppF)&_ilw>{=ldrGxBFu@7O3~puj+y`w^zk;H+Rx9Pq8nWut>e8(`9z
z=#LTY{%#I^v_CAhxy?}BY*}U4M%#GIK3D8cpa^}PT~2&>&S`>MbW9!=n}Wf01Kpl@
z)4h3V_0mDDJNKwxG#FHV(V%A7_RI9)grI*wV2#rh1-fZ)uA5m$clET3Ny-vM;Gn6P
zNlu2Z*tpsQ9ni%p?o`9;Qic=NF6k@_VmuM(2cdG+4ySKc>>k@((UK`=Yo6a)HWMwF
zSLwdn^=Uqpd)xds+e#mjxU$>s$bf?=LC_wdta7#_7;&9|7TOOCRUcj`x`-RjxVT`5^3jlQpzQN44LJbBZH3jS}^*4QPlTf8<)m33WEU9m<(wO>K>
zQvS}x??d4+0D96ZLbJrCY`bg2o4?1uN$pp#o8h-10j5Cz=>KxMd`M0twsD}tDp__uV*ZQeO}*O
z{4SqR-Dyj>Y=(mwk==Cy}s`?0QN|6b=J^-SpSm$(OYtsw3?r{{t*f}
zceAss@I9GtWJwF9%8zAyhG`15mD?Q%SDp)5`CW+u-l`vJMB
zx%QAL#O+>51AS?rIaOpXI}!gLAhcDFmJOq~nBzFVr|xq8O(58xW11F9eDhCc=YX7u
z6i{ha@dQ|OPU@TsPb{NTu14A6U@7V>X-N8&O5C+Eo|72~`L-(v`Xud!1C}XK#GDsB
zGW>Kn?{u;bK+zrkhFUAS3J6P9>ZgK
zveDg%p|AM#Vt+^5sD1dR$eLmj1z|F~I?s2pCWpwvyEjFT!*&9lsD66rEqpS-$}qYM
zf`hH|_H4a`shgI=o4gfaC{1_b4FU4C
zG5g-m6XwG_A>}wYPNvT3!*&j%_d7^g&H+&EIP-}+tVdo3ITv6icr*2EDT?<4?i8cb
z^6U4>2<+2G%&Hd9rf8a@3~+6kILKQo+|7mj)nZC{OWCT}zZxSP%sdTET~q8Om0ChSfBs>b^B+$9HNIG~b~XkJHX4F$x`0zN*J%+eIIHgv+yITkr$
ze%$xb*|h4NtFR2{ZuY?k53nSBQoLQ2{m2x<#$|4#ICn!3?sTjamX$8V=l$o_G!?A8
zB+V|*SKJ6-&sT=0*;Pkr_cCk>ISH_e9w3O3P-jQ|B0D1Tde~`)A&5?
z)n?mD^8Zv83RzH#spL|>A1bO2F?1eKTmAprc?HXHB(RRtg~E>P>_C7?F~6*lq>1OHonK&Z+|aLYXIQ*>yaZ@G&y$s;CdTje
zNatVHsY#l?fN{G%EDKYM0S^RdiuS{C$7-v@FV>^lL>p@*)EnBx@&L)%J}UD+CYBw|
z6R|4g?~?WL$%s5>hE0*4_HTtx?b%`@%Em?Df8wSU|4|e;(+}sq6OWROJ1Om3zN40Z
zs)=yLYA(Qi;C#YWd^8MKD0*A=9-&
z_8JFwRe*Nk|5YC{ZpipdBrTP>#=uNzEGO&cSzk*b=EsQKjw2Ot)%i=0Ktv=&Q$GKaUtqwxGq-zV#gu4@_jvV1WmI`Yogu9
zC5m%j#N!G&&Rqs22vEPJv<&-pzM)VY!7xK^W|c_xH#=tMOw>u$Me>;dyB!e@%e)GDjEg=^ZF0RCc4jmF9qrAJ{Gj!+z>CC5TMKpBHc3%mU?^oX-6&nK86
z(6nBf{IaQ4*m@j%7O!?@6(zTcj6BE$ke+ho;$WU70B(L
zu72MW`e4J#^UjojTYbBKOUq}&KyXo-8}@`@b5{nRwa_n5PHYWn1OuCAd?7s9i+=!t
zfkbW}cx0`q_$Mj{Ny2b&A4i4-Ya;&gqWx2AZ(dWmo!I6XqiT2}!5GwL;ioE3q8!S=#xZM9`u
z(|6kam8a!NEqx;+W~e|OBzIJJmNd09l8MDTRp2qw(xmwsnCbBZ^yC=pn34V~TE)|_
zxRBpeWXBm`1V{C^A;-3G*VK;+YvC1uocOa0I}DH-^Vhg0lTsIY9*+P{Nvb)gvkx)G
zr&3fLpV`C5Pw4&1Tnht9r$O{2U>VFV&1V+$f+jXxujE0oJ13d_4yWN6vnw6>?D6}2
zP}@=>?}90@}5icFBh*p;xt
zIo()%)`U4IE7l$#zda--<=6qkgiv$fb0vj5Z4;`+cVIN8R+GA%6bXX4+0l)^vDG;X
zjewkCU~)f}{f_M?ScVw_-Q9}K==2RDUk=6FuTy-D4?f{MZ*~Ei!7M}OJL`XfcDn!W0#-K~kQU6G!Ex&&V$k(v_l|<4Oc9~Yv;_sCP4;d|DGeOsq9PDmk~pfx
z=!_{*_nt!9=q$bN?4P?o=SrY`AsR(>>q3Uf+SEV@%a#UeL1EPPH_qh~3!_m?nQ
zF;F-OIUwGEtQfHB8o2K#Ogc$uRi%+W{AWig_SLCfN~&D3R9v>!mqr1d7x}An|Jh9Y
ziApW1OeJ(A5r!Z3`)B@A=e
z8#0+C!wo&{Z>}_`NWz&1qcxx
z>$$Tj>#b-1*KBG>!5CCTV7C0-kzkoyP>O)7tX<1M+X!fA5kE8&cFQtmh@0#L1ga4`PvH<{>H!5h2$*
zyRj81=HAlfqEeuQZvlT2co|
zQ4RnGqVRVHDK%D<9Vp>D2)d34m^|hvxF}y~q5N=NTkY>fg*oPT`O__<94+gNQo-K~)y5yqhe}_pOoi9l{aX+PQXl=G&`{m6D~-|%jL|7d
z@%=JgnO$T5Lw~}F6c0G`OmTW8JhKI&`V{o)7<3|r!_Peg3I*fXwr15pgY`~Dvx!dY
zimMcS)&EH#4@vZB9k+UZB_~cd2^}4BkuEPP;lC)bIcmjf^Vq{RsYs@@*Kv?&yfb_^
z+V~S4l#UC+2c1ZYUqik5dz@;%af*U1f?@eILQcCWmz&n9QK3)0g)~E0IdLop^^b&q
zqUL7z38yK`5%12|a;btV2$ti^RbeSkWCC-p!?}t=!rTUIwN_~INkHYY-~lCgz~i!-
z5=WTAIWH()dGm8DGL!!m^bwvPKPJRJ2WW#bVjGllU_K`&=6%4n(PRB;m&v;0i?vDj
z7rUvKHNB7j2YRGFhS%31y}acY|Fj#g016H%wZAo&k>okVU`J8*(Lgxq8N+F>+FF?W
z%jnpTRnHLp?+w{oFF0jdatwe$cazWQE!_%?${m-0@h?7;bcUi*JHm6W%ejjEL^4WV
z1dY0elcS{$_rfdy5^gLP;lXEe2f#%^X9TBYtKDkbM>lYs%U;o&;6gy2SCFu&T|~^tkSR_
zxxdB+ABl(*{$tk+jqieKmoOkL2lUU(--0kdVxY%cd@u`>%
zCPSPc_~(QxbAVkZn5TofnejfcN4own`G-nYwNG_hO7IuRv5ny)hP*Bc6od-;6V96b
z#OB*aD!JJzFYu1xc&wqNY}b+hB<})9`UN}1}11-
zjiL;f7w@l~H0`QZ>`6X*c0t=r&zAEuAPVyKovfD3yA0^ZTo0m3cp<%l44SRRD|Lk2
zdVJ1>2?R4NX_Y_S1o%f}sW^#3`oMDaHS2ZxgT%Q-QdZ5?;>qfD_S%9arwdn|d8YEH
z%K%OJYrrjWk9lu(d1utL_)@u2c9~*3)%w+)O@oBAbxq^JMBEH@!%YfD`S4v58VYP$
z!`JZBNGu!Z`E$@<1BP>4foqKgy=+1(*jPG&$px^MRHaKzr;gd_V!eVvTQ74OHaIgi
zx9J2lbsmDGZLP0Ls;Y9DIw-MBJP`NRi3sVds9@s!ih675x2f8oSFb>ys3Oxk7X~Iz
zMIf!>hx4jg7^8o^9Fi9>UrJ$O~FtSdo_4zPX@HRC-WtsRQ%Ql?}buEv3g*AMbP|PA`Zs@;?_V8yw-478)()F
zJW9!*y!l4CZmT^*8}jQl;&`?otLbnAPWxD`CJ%Ykq9oa)y6&^9NKTZU
zG&H1M#c6wq^(qH$T&5(-4vz@-?Aqqm^ft#C%Jdx5e`Ic0Qqgb4ZKaTjY<(Eg(*YAQ
zf$3?UPhCXl-)1yXNQgFD=Lz5+qE(Vz9efIpG|KISIb3{QjA4c0k=Hy7Vh6kH|n;7OeTj0JL#nDxF$CGEPd8LG@1F*x8fH~{K5&+VV^O!UE+{+MU%0l
zOU2^4J`b6^x0+xg?nXr8WLv9x8-10vmCj}9a0CgMY4o;7%R?mv2
zw>)@ej8T3#UX{eAXQs?uvGZu_Pp^+s2h5_A*>OZ}ke;9>EjSymifhff_I(D>o>7`s
zVU+nB7H1?=?Rl{AC>to38MGw;7eyF3>W1BL%(e5P0kEB?Nj9x`i
zdl_fYpZ8R>}8CyCGZ1Y
zfGqeCUmsAW-X^Y;SChjv-tt^4=$AKa{Dn@?upczq?>o)@iCMZZc4UXW#OnXic*2q1
zo!gEOE+uH#s~E2@YeRCEDgEuNMtXRZnlWB?$TparX%HHpkF!AowVMvp&^Y|msV0{piGf<*k>4+3<2+XDPn`TIE{
zRy43FkV3E3(V)g)_3>jr-O;It=7H@%p>6!8dUKAIGCQq!}Fj
z%+_@@c#!Oq^KNhSV$YpS(BKJ>
zZ{MsN5}mv|(3fp`rV(%2u~*Bk4h@Za`x|Lqq4N0$Oji^4$H6DHysc{1+>J*TR?KeL
z^iPo8TVGK`1Kl;xpL6M*)vuE|Sd?S5(tr<07n~H{UulEpzk*+sdJlGzHc7Yh@XM_n
z4S$@jPcrY03uF#ukM_~=K0qT1ZFP}qKIw1?FR)!-ztX<9`5O%Rf=`!ql1eJLEagjv
zS6(1FaG-Uk>WpFf1xdXUwCH9nPX2PxU`u0YluDoiLO%$R2|3WitHKE&X_bCT{f_F(
z+bpzErmMs2!pkP7$+$yP{zWT6J3}ngx8L}WTo;PHn+km2*@#awr)dL;tskRg5T)9V
zKVc$0@*O98S6cF@UR0^s)c33^U1Qvf3U@&lOy~-RH^K;8>hYIHspfIa4sabr8BNjF
z>>7DQ(>TB(0=m%eCuo8sY_Q!HgPYE3VLiewE@T~ETEwHHrq_D|eNTE0E)&r-=~Dft
zuZ$z0*K_o2Q9(37{&9h}6cB=NtSj_nB#nZp{aDbSDFm`Y^6nv3`W=7NLb6wN>+1n8
z_URztBmtMwE%lQ=r^n^XMurdfF|#*%{^3gR&y@Wf>@9FM@{1dTcj}DLCkHE6%
znhY)~Gz`saE$I@Z6lzE0lhnVYVFWr`Y{R+9WkO2x_%Ht4%My6|lG^4S}KUVlG$qyDDZvFeL-YWA<`cpS!2t6wP^yH*~uz@z{LHT)3;PwyB
z)3??yZ~6SM_$Tm8G2R%IGPu98y~?Ng>of8qbLOWblUWS>j}`%Wxlbn2(bRU>sy#d;
z@J22^XXo@I_i4t;D`^ywdpg3Be|dZjv8i;PY7dgJwg
zp&e9vD7`$>ECAiVPkYveVJ2R!4sztx|A-B3#02?5S23t9s_%p=JxP673~RX^eF!Vt
z(HlHpt1xT1q--ua#u?~BM2Qnnz7y1!_2(bs`8Nlb9cT^~H`@Lgg+=XTW|F&RKb${K
z_B>&{3Tr2KKP)D0G(wY!L6vcHKFz5WVY`DbffF9k>j$-5Oi}h7v@2(icMp19e}x@6
z=;mFe$jqWz)GU#ftZmQ8{AjIlS6aQ<9x=57g|KmZHvCEX$o*U%c}QNuI)2pRT!&5y
z;w1)c6tlPNNjthKJ3OJCI>l`({g8z^>daA3`P<|E|Frt^^KhWQ42h;4l
zN84?gwTOjlezkA>aOn@TKBg*{ZP5{QS?Qs%#ij2bGcaW=UMDASg&qc0nr0zo?8xaq
z4IT>j`QQQ1gBdjF6;aE$m?$4xkTj2Tn~!h!ItgusfZC=cq|VDy6QNDgusc;}?x0(6
zl>+xewJ1hT#AzGN)za4zwrB1qjm^f&@!ct^#(o35NWX}1OmUyj#|tR_c)YH=Hzs=`
zrgZre^eG)g=)K|(8#AON`f$Z{ieF=6s;maNCKdJ6(v$3tm@o4nUY4*VYtfn|*GX9|
zL#?Jy_!IazTc33>JG^_8ZG)bfTpA?(w5@5R;Zv*vR@x?6N0<3m6@7I9*T`U@{9vmt
zjk85{Frge+y`CiBQZAZv?-<5XF>HT`A8<{G;PyKB-5}M)xp`dFf=)KW@kz**X5x7z
z$IOT$r&3d%B^Em*eRZ_$L9uT=q{`6EqC0=tnd>ypUAOfY=SVb8ZPMkD8uXgie?hh8
zieZDi81(5osDBu;44w~ALOP#X^tEqq3|7gA(%Q5+n3!~b)eKRz`_RTqRAhGZH|>*%
z-2fgzPM>(jjr$NW?Xlj+{KHtgME=KW>Uw!?a|G98y)A8r(WQy{Wj(ZkpNIP=_NHMx
zHuMWNsGi~zj@e$8p_wfIK0FX>Ug8tMy98U6P@DK!B7hDs@D5(WK6Wx5+>nX}3*ZSl
zFGU%Ye{Ez)9&{*pLa{h;x0vdFJ2@AMdEPP`nb+B}K-m0TI1fT!4w10}mt(}$@xOdH
ze-;R%S)wxFTq2o7q7R1tRI5fe;djNb5Yvh#?oWkU6y3?+y`{A??KC7ZUZN}IjF?qJ
zY`7YtH7_`q(IpMu^c1)!Tq2=^BM2HAb>r9SvDhGY2z>>EneR&N>~18Y9ZwH)WgCe)
zNsb}hMZyw#e(-HSSrgomUMX1~1^t%LS5AwidFP`M~?9u5BLSF%H=
zuKbiv5sScx>G<70XG<1;Fn|JB9fb5#6Cd*~8#vSouCJlMnxpD_p2&l=?z$T0XgGCB
zKqJJuIInxnJ&}G@-MHA#bc9g+xrW?wDEsDJnPne6kEhe_o(3hq=h8&6*t}ueH}1_#KHp2y(^?Ee
z1I+YMxXcY8W@x_c!7IJ}8UwoIZLgxqZRQ_{4X=d@475N!0q8Qz-tj0_RQGRuv0LMx
z)~1CP{WqrUViiT+4wy(nn2OCG(SHqzl
z0|$cdURyZCDCxKiG?vMYgt593v{(OrJ&SsKnpV&~U6i=@Nl!Ev@(mLe#KmX=k>9w<
zd?62G*^7{4JJyBCc%KEi_e>8PY61i2U+(A2lsGCC^8MYguDx
zU^~CFtKfD=4_<)B-_tHrdVEuuEjh%w+!S>lCR1P@t{gm&bfm8Ny$z*%l)zeLfo^EH
z!#qDEK;YA*AF)J2?}#@~kjlB=`Ygo0$w~5!?KgL)1}%AJt>ffdcTH@IjfAL$BHI=jU*O1$6^-LK2q-+BvA#LpAAtY
zl9?se?X19x6s<5J&tIu?3ByMkVw?iHrX6|~FKoB7yWe{@
zbhfll)R}+|MV2%xK?zRUJIsj=3UcF)f8I}2h<
z?>~!xiFr9oRkZX)^~1;q;?Aa0+v2il=J*7_3+urX^JVGUGlXJ4psqJi8Lb
zE!7}85E2-akw@0nb8gIe#U5>oR~O0162BPZET9_s>OQKYfpa&;(ixRv%lg}HNZdQ(
zDR6Si5*sK=9b}OqKupX6V{pAHB;X^nXg^L2`i_uHn9;*J%H0Y=AW?o|PJaB?NIPw)
z#wel_f8*)@@w&X+J+ki
zRCUmNXMe)4w~@NNe6zbNP2Sth_-9L6Lo%eM&w(iYjKyDo%YZjcT96w%bfp7+hdWO4
zX4_&ElQi=fh#NM9K5H!c%z$JKyxY(_f^S~QoVZntwZt$4F&HMfxk}RYTnR=g71y0T
zZ=2x1vGMfi%{rVA@!UJO?}+2Mva#s84|-xFBn?qo$i)Vcf~qKy14Eri*>NtVQ7Dew
zU>>c9VQ8tESKP_f@#aOZz*f1fj-k%9%b)%|YCT~g4PI8p?5;BbO)+Vw6`u6`HkLFzN()s;6@hC=u2a?p`fmd2a6{!3S?Ub#7Fkz*@n0ecde`|WZNmVJ?Myz54
zELt(iEu#}Kx6+<>!JtHs6vhViba5y%Yfj7vj%Ee{BIqKvFo!A)<
zHVv&|vuAt|7s&~k)~xzHtS43kkwKH;z~ItGtP&_Z9fuEvSuk~cZA5X?kWqR>4XWFh
zEO*LC>wrC7)!$(hsr16_i_hwzk3I=_^p`#SQRn}
zL$2#McWVnOcD*!X%2WC8>B=Yu(x{lvA{XEFujLtK9~*T8(6P0rPdYJEI{ZCih00R(
z*JdU*)CLD?CbZZSC?WiEgUH3|;|$?KTj2K1(L!-9GY9zx{fbt{!xB`THT85h^$M3A|Ln!La~*A6GjO1EskKqe)<vcQ1KIFSoNRMdk-nj7Z@y^u!q3`g>5IGp?Lh$+b5|Jly{)UN
zs-{=y6=A~L+)j!XljiUiU|xh@B=3K5cuJ-Goyv}So}^OuOLjjiY^cw%sd>dtbrVVy)Uugimh%yTI0d>zLW@#`rgBjFk#*l0fgn-zbrz8j{1Fc6GPAU!naGdePDzEMbKc
zOTnWo&!Ob$-XB7q@X7rhTN0jI;h>*aiJisCKGC7Ug3M&(lKFJOb>o3w`4I7HPA>HP5fyZ%S`##??0-SZ1x9EXxXW{Pf=
zuP>gV?sTQ&Yh&h36UXPaWsdJD$lQV*jeCEw?@k^Lu(pg35i8saU$hZ7voM|fR}Nky
zpDFwU=ib>^1wJ2$?;YL(5UHvq9x}!b7O^?{_aQmI9R@{l%akXEP=a1ohwu(W4d)Pt
zS%1)H%1qI=roEpeC{SNLI6`CrPryM}E9v)Ncltp3%El)*yEAVNp+0+WN*;
zM0gvy;1UnELa|);X%8w(y5D9oueWF@4u8=|qEBbx-OPd4f8lB0j6AC2dUpl$DRxip
zKN~_9PWSye$CT9H99TrYF62J~DR<1ED^QL`LQc%S6-?i;h#l#Izd`85@+PWT22kK>
zd2bRi5!@f%Otxo>`dM*={L(w77IcQnQ0Dj>rnv(r_=N4xm?