From cb3a501280b14917fd1c594f25521a4979ca20fb Mon Sep 17 00:00:00 2001
From: Andrea
Date: Sat, 2 Mar 2024 17:06:00 +0100
Subject: [PATCH 01/30] [] Added confirm modal for badge reminder email feature
---
res/scripts/adminManager.js | 21 +++++++++++++++++++++
tpl/admin.html | 4 +++-
tpl/components/confirm_action_modal.html | 13 +++++++++++++
3 files changed, 37 insertions(+), 1 deletion(-)
create mode 100644 res/scripts/adminManager.js
create mode 100644 tpl/components/confirm_action_modal.html
diff --git a/res/scripts/adminManager.js b/res/scripts/adminManager.js
new file mode 100644
index 0000000..f6584de
--- /dev/null
+++ b/res/scripts/adminManager.js
@@ -0,0 +1,21 @@
+function confirmAction (intent, sender) {
+ if (['propicReminder'].includes (intent) == false) return
+ let href = sender.getAttribute('action')
+ let intentTitle = document.querySelector("#intentText")
+ let intentDescription = document.querySelector("#intentDescription")
+ let intentEditPanel = document.querySelector("#intentEditPanel")
+ let intentFormAction = document.querySelector("#intentFormAction")
+ let intentSend = document.querySelector("#intentSend")
+ // Resetting ui
+ intentFormAction.setAttribute('method', 'GET')
+ intentEditPanel.style.display = 'none';
+ intentDescription.innerText = sender.title;
+ intentFormAction.setAttribute('action', href)
+ switch (intent){
+ case 'propicReminder':
+ intentTitle.innerText = "Send missing badge reminders";
+ intentSend.innerText = sender.innerText;
+ break;
+ }
+ document.getElementById('modalRoomconfirm').setAttribute('open', 'true');
+}
\ No newline at end of file
diff --git a/tpl/admin.html b/tpl/admin.html
index 2c1b0fb..d234b31 100644
--- a/tpl/admin.html
+++ b/tpl/admin.html
@@ -2,6 +2,7 @@
{% block title %}Admin panel{% endblock %}
{% block main %}
+
{% endblock %}
diff --git a/tpl/components/confirm_action_modal.html b/tpl/components/confirm_action_modal.html
new file mode 100644
index 0000000..db9fb31
--- /dev/null
+++ b/tpl/components/confirm_action_modal.html
@@ -0,0 +1,13 @@
+
\ No newline at end of file
From f4ebc83a9b9204690ccdc544b109de362201a082 Mon Sep 17 00:00:00 2001
From: "Luca Sorace \"Stranck"
Date: Tue, 12 Mar 2024 21:35:52 +0100
Subject: [PATCH 02/30] Fixed problems with canceled orders
---
ext.py | 12 ++++++++++--
metrics.py | 18 +++++++++++-------
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/ext.py b/ext.py
index b891f2a..83bfacb 100644
--- a/ext.py
+++ b/ext.py
@@ -35,16 +35,21 @@ class Order:
self.sponsorship = None
self.has_early = False
self.has_late = False
- self.first_name = None
- self.last_name = None
+ self.first_name = "None"
+ self.last_name = "None"
self.country = 'xx'
self.address = None
self.checked_in = False
self.room_type = None
self.daily = False
self.dailyDays = []
+ self.bed_in_room = -1
self.room_person_no = 0
self.answers = []
+ self.position_id = -1
+ self.position_positionid = -1
+ self.position_positiontypeid = -1
+ self.barcode = "None"
idata = data['invoice_address']
if idata:
@@ -106,6 +111,9 @@ class Order:
self.phone = data['phone']
self.room_errors = []
self.loadAns()
+
+ if(self.bed_in_room < 0):
+ self.status = "canceled" # Must refer to the previous status assignment
def loadAns(self):
self.shirt_size = self.ans('shirt_size')
self.is_artist = True if self.ans('is_artist') != 'No' else False
diff --git a/metrics.py b/metrics.py
index 43f037d..8f5da5f 100644
--- a/metrics.py
+++ b/metrics.py
@@ -1,6 +1,7 @@
from sanic.log import logger, logging
from logging import LogRecord
from config import *
+import traceback
METRICS_REQ_NO = 0
METRICS_ERR_NO = 0 # Errors served to the clients
@@ -47,7 +48,7 @@ def getMetricsText():
def getRoomCountersText(request):
out = []
- try :
+ try:
daily = 0
counters = {}
counters_early = {}
@@ -61,11 +62,13 @@ def getRoomCountersText(request):
if(order.daily):
daily += 1
else:
- counters[order.bed_in_room] += 1
- if(order.has_early):
- counters_early[order.bed_in_room] += 1
- if(order.has_late):
- counters_late[order.bed_in_room] += 1
+ # Order.status must reflect the one in the Order() constructor inside ext.py
+ if(order.status in ["pending", "paid"] and hasattr(order, "bed_in_room") and order.bed_in_room in counters):
+ counters[order.bed_in_room] += 1
+ if(order.has_early):
+ counters_early[order.bed_in_room] += 1
+ if(order.has_late):
+ counters_late[order.bed_in_room] += 1
for id, count in counters.items():
out.append(f'webint_order_room_counter{{days="normal", label="{ROOM_TYPE_NAMES[id]}"}} {count}')
@@ -76,7 +79,8 @@ def getRoomCountersText(request):
out.append(f'webint_order_room_counter{{label="Daily"}} {daily}')
except Exception as e:
- print(e)
+ print(traceback.format_exc())
+
logger.warning("Error in loading metrics rooms")
return "\n".join(out)
From d9748709634287ab7d6d217c39b6c4627f3d97da Mon Sep 17 00:00:00 2001
From: "Luca Sorace \"Stranck"
Date: Wed, 20 Mar 2024 17:28:35 +0100
Subject: [PATCH 03/30] Fixed various bugs
The unconfirm rooms incident....
---
.gitignore | 3 +-
api.py | 15 ++++------
config.example.py | 73 ++++++++++++++++++++++++++---------------------
email_util.py | 10 +++++--
ext.py | 4 +--
room.py | 2 +-
utils.py | 9 ++++--
7 files changed, 64 insertions(+), 52 deletions(-)
diff --git a/.gitignore b/.gitignore
index 23d733b..90a11f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -168,4 +168,5 @@ diomerdas
furizon.net/site/*
furizon.net.zip
stuff/secrets.py
-backups/*
\ No newline at end of file
+backups/*
+log.txt
diff --git a/api.py b/api.py
index 2e13108..d14f5a5 100644
--- a/api.py
+++ b/api.py
@@ -9,7 +9,9 @@ import random
import string
import httpx
import json
+import traceback
from sanic.log import logger
+from email_util import send_app_login_attempt
bp = Blueprint("api", url_prefix="/manage/api")
@@ -221,16 +223,9 @@ async def get_token(request, code):
request.app.ctx.login_codes[code] = [''.join(random.choice(string.digits) for _ in range(6)), 3]
try:
- msg = MIMEText(f"Hello {user.name}!\n\nWe have received a request to login in the app. If you didn't do this, please ignore this email. Somebody is probably playing with you.\n\nYour login code is: {request.app.ctx.login_codes[code][0]}\n\nPlease do not tell this to anybody!")
- msg['Subject'] = '[Furizon] Your login code'
- msg['From'] = 'Furizon '
- msg['To'] = f"{user.name} <{user.email}>"
-
- s = smtplib.SMTP_SSL(SMTP_HOST)
- s.login(SMTP_USER, SMTP_PASSWORD)
- s.sendmail(msg['From'], msg['to'], msg.as_string())
- s.quit()
- except:
+ await send_app_login_attempt(user, request.app.ctx.login_codes[code][0])
+ except Exception:
+ logger.error(f"[API] [GET_TOKEN] Error while sending email.\n{traceback.format_exc()}")
return response.json({'ok': False, 'error': 'There has been an issue sending your e-mail. Please try again later or report to an admin.'}, status=500)
return response.json({'ok': True, 'message': 'A login code has been sent to your email.'})
diff --git a/config.example.py b/config.example.py
index 1d5a880..247ebec 100644
--- a/config.example.py
+++ b/config.example.py
@@ -46,6 +46,8 @@ DEV_MODE = True
ACCESS_LOG = True
EXTRA_PRINTS = True
+UNCONFIRM_ROOMS_ENABLE = True
+
METRICS_PATH = "/welcome/metrics"
# Additional configured locales.
@@ -65,51 +67,53 @@ SPONSORSHIP_COLOR_MAP = {
# Maps Products metadata name <--> ID
ITEMS_ID_MAP = {
- 'early_bird_ticket': 126,
- 'regular_ticket': 127,
- 'staff_ticket': 155,
- 'daily_ticket': 162,
- 'sponsorship_item': 129,
- 'early_arrival_admission': 133,
- 'late_departure_admission': 134,
- 'membership_card_item': 128,
- 'bed_in_room': 153,
- 'room_type': 135,
- 'room_guest': 136,
- 'daily_1': 163,
- 'daily_2': 164,
- 'daily_3': 165,
- 'daily_4': 166,
+ 'early_bird_ticket': None,
+ 'regular_ticket': None,
+ 'staff_ticket': None,
+ 'daily_ticket': None,
+ 'regular_bundle_sponsor_ticket': None,
+ 'sponsorship_item': None,
+ 'early_arrival_admission': None,
+ 'late_departure_admission': None,
+ 'membership_card_item': None,
+ 'bed_in_room': None,
+ 'room_type': None,
+ 'room_guest': None,
+ 'daily_1': None,
+ 'daily_2': None,
+ 'daily_3': None,
+ 'daily_4': None,
'daily_5': None
}
# Maps Products' variants metadata name <--> ID
ITEM_VARIATIONS_MAP = {
'sponsorship_item': {
- 'sponsorship_item_normal': 55,
- 'sponsorship_item_super': 56
+ 'sponsorship_item_normal': None,
+ 'sponsorship_item_super': None
},
'bed_in_room': {
- 'bed_in_room_main_1': 83,
- 'bed_in_room_main_2': 67,
- 'bed_in_room_main_3': 68,
- 'bed_in_room_main_4': 69,
- 'bed_in_room_main_5': 70,
- 'bed_in_room_overflow1_2': 75,
+ 'bed_in_room_no_room': None,
+ 'bed_in_room_main_1': None,
+ 'bed_in_room_main_2': None,
+ 'bed_in_room_main_3': None,
+ 'bed_in_room_main_4': None,
+ 'bed_in_room_main_5': None,
+ 'bed_in_room_overflow1_2': None,
},
'room_type': {
- 'single': 57,
- 'double': 58,
- 'triple': 59,
- 'quadruple': 60,
- 'quintuple': 61
+ 'single': None,
+ 'double': None,
+ 'triple': None,
+ 'quadruple': None,
+ 'quintuple': None
},
'room_guest': {
- 'single': 57,
- 'double': 58,
- 'triple': 59,
- 'quadruple': 60,
- 'quintuple': 61
+ 'single': None,
+ 'double': None,
+ 'triple': None,
+ 'quadruple': None,
+ 'quintuple': None
}
}
@@ -129,6 +133,9 @@ CATEGORIES_LIST_MAP = {
# Create a bunch of "room" items which will get added to the order once somebody gets a room.
# Map item_name -> room capacity
ROOM_CAPACITY_MAP = {
+ # Default
+ 'bed_in_room_no_room': 0,
+
# SACRO CUORE
'bed_in_room_main_1': 1,
'bed_in_room_main_2': 2,
diff --git a/email_util.py b/email_util.py
index 4687c5b..7b0fd01 100644
--- a/email_util.py
+++ b/email_util.py
@@ -48,6 +48,7 @@ sslContext : SSLContext = ssl.create_default_context()
smptSender : smtplib.SMTP = None
async def sendEmail(message : MIMEMultipart):
+ message['From'] = f'{EMAIL_SENDER_NAME} <{EMAIL_SENDER_MAIL}>'
await openSmptClient()
logger.debug(f"[SMPT] Sending mail {message['From']} -> {message['to']} '{message['Subject']}'")
sslLock.acquire()
@@ -87,7 +88,6 @@ async def send_unconfirm_message(room_order, orders):
message.attach(plain_text)
message.attach(html_text)
message['Subject'] = f'[{EMAIL_SENDER_NAME}] Your room cannot be confirmed'
- message['From'] = f'{EMAIL_SENDER_NAME} <{EMAIL_SENDER_MAIL}>'
message['To'] = f"{member.name} <{member.email}>"
memberMessages.append(message)
@@ -110,8 +110,14 @@ async def send_missing_propic_message(order, missingPropic, missingFursuitPropic
message.attach(plain_text)
message.attach(html_text)
message['Subject'] = f"[{EMAIL_SENDER_NAME}] You haven't uploaded your badges yet!"
- message['From'] = f'{EMAIL_SENDER_NAME} <{EMAIL_SENDER_MAIL}>'
message['To'] = f"{order.name} <{order.email}>"
await sendEmail(message)
+async def send_app_login_attempt(user, loginCode):
+ #TODO: Format a proper email and add it to messages.py
+ msg = MIMEText(f"Hello {user.name}!\n\nWe have received a request to login in the app. If you didn't do this, please ignore this email. Somebody is probably playing with you.\n\nYour login code is: {loginCode}\n\nPlease do not tell this to anybody!")
+ msg['Subject'] = '[Furizon] Your login code'
+ msg['To'] = f"{user.name} <{user.email}>"
+
+ await sendEmail(msg)
\ No newline at end of file
diff --git a/ext.py b/ext.py
index 83bfacb..1a5f791 100644
--- a/ext.py
+++ b/ext.py
@@ -44,7 +44,7 @@ class Order:
self.daily = False
self.dailyDays = []
self.bed_in_room = -1
- self.room_person_no = 0
+ self.room_person_no = -1
self.answers = []
self.position_id = -1
self.position_positionid = -1
@@ -93,7 +93,7 @@ class Order:
roomTypeLst = key_from_value(ITEM_VARIATIONS_MAP['bed_in_room'], p['variation'])
roomTypeId = roomTypeLst[0] if len(roomTypeLst) > 0 else None
self.bed_in_room = p['variation']
- self.room_person_no = ROOM_CAPACITY_MAP[roomTypeId] if roomTypeId in ROOM_CAPACITY_MAP else None
+ self.room_person_no = ROOM_CAPACITY_MAP[roomTypeId] if roomTypeId in ROOM_CAPACITY_MAP else self.room_person_no
self.total = float(data['total'])
self.fees = 0
diff --git a/room.py b/room.py
index 3e1382f..6fb9dc3 100644
--- a/room.py
+++ b/room.py
@@ -326,7 +326,7 @@ async def confirm_room(request, order: Order, quotas: Quotas):
room_members.append(res)
- if len(room_members) != order.room_person_no and order.room_person_no != None:
+ if len(room_members) != order.room_person_no:
raise exceptions.BadRequest("The number of people in your room mismatches your type of ticket!")
for rm in room_members:
diff --git a/utils.py b/utils.py
index d347419..d82879f 100644
--- a/utils.py
+++ b/utils.py
@@ -205,9 +205,11 @@ async def validate_rooms(request, rooms, om):
for rtu in failed_confirmed_rooms:
order = rtu[0]
member_orders = rtu[2]
+ logger.warning(f"[ROOM VALIDATION] [UNCONFIRMING] Unconfirming room {order.code}...")
# Unconfirm and email users about the room
- await unconfirm_room_by_order(order, member_orders, False, None, om)
+ if UNCONFIRM_ROOMS_ENABLE:
+ await unconfirm_room_by_order(order, member_orders, False, None, om)
logger.info(f"[ROOM VALIDATION] Sending unconfirm notice to room members...")
sent_count = 0
@@ -216,7 +218,8 @@ async def validate_rooms(request, rooms, om):
order = rtu[0]
member_orders = rtu[2]
try:
- await send_unconfirm_message(order, member_orders)
+ if UNCONFIRM_ROOMS_ENABLE:
+ await send_unconfirm_message(order, member_orders)
sent_count += len(member_orders)
except Exception as ex:
if EXTRA_PRINTS: logger.exception(str(ex))
@@ -262,7 +265,7 @@ async def check_room(request, order, om=None):
room_members.append(res)
- if len(room_members) != order.room_person_no and order.room_person_no != None:
+ if len(room_members) != order.room_person_no and order.room_person_no != None and order.room_person_no >= 0:
room_errors.append((None, 'capacity_mismatch'))
if order.room_confirmed:
allOk = False
From bc366c85e55ed49c3cdf875ef03cbd0ed9573083 Mon Sep 17 00:00:00 2001
From: Stranck
Date: Sun, 14 Apr 2024 11:35:41 +0200
Subject: [PATCH 04/30] Fix daily people not showing up in the webint
---
ext.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ext.py b/ext.py
index 1a5f791..35aa170 100644
--- a/ext.py
+++ b/ext.py
@@ -112,7 +112,7 @@ class Order:
self.room_errors = []
self.loadAns()
- if(self.bed_in_room < 0):
+ if(self.bed_in_room < 0 and not self.daily):
self.status = "canceled" # Must refer to the previous status assignment
def loadAns(self):
self.shirt_size = self.ans('shirt_size')
From 0d6789d3071e15340f7823fe7f588fe37f4239f7 Mon Sep 17 00:00:00 2001
From: Stranck
Date: Fri, 10 May 2024 21:50:51 +0200
Subject: [PATCH 05/30] Removed shuttle bus
---
tpl/welcome.html | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tpl/welcome.html b/tpl/welcome.html
index 89cbd50..390f721 100644
--- a/tpl/welcome.html
+++ b/tpl/welcome.html
@@ -88,8 +88,10 @@
Shuttle
- This year, a shuttle service operated by the tourism company of Val di Fiemme will be available. The shuttle service will consist of a bus serving the convention, with scheduled stops at major airports and train stations. More informations in the dedicated page.
- Book now!
+ Due to the low number of requests, the shuttle service managed by Trentino Trasporti will not be available. Those who have purchased a bus ticket will be refunded directly by the transport company
+ On the Furizon Telegram group, there is an active topic dedicated to car sharing, and the staff is available to look for custom alternative solutions. We apologize for the inconvenience.
+
From 938bc683833a7ca239997ec97a96a46c13029738 Mon Sep 17 00:00:00 2001
From: Stranck
Date: Fri, 10 May 2024 21:51:14 +0200
Subject: [PATCH 06/30] Added extra log in case of generic errors
in update answers
---
ext.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/ext.py b/ext.py
index 35aa170..dd36d32 100644
--- a/ext.py
+++ b/ext.py
@@ -222,9 +222,13 @@ class Order:
res = await pretixClient.patch(f'orderpositions/{self.position_id}/', json={'answers': self.answers}, expectedStatusCodes=None)
if res.status_code != 200:
- for ans, err in zip(self.answers, res.json()['answers']):
- if err:
- logger.error ('[ANSWERS SENDING] ERROR ON %s %s', ans, err)
+ e = res.json()
+ if "answers" in e:
+ for ans, err in zip(self.answers, res.json()['answers']):
+ if err:
+ logger.error ('[ANSWERS SENDING] ERROR ON %s %s', ans, err)
+ else:
+ logger.error("[ANSWERS SENDING] GENERIC ERROR. Response: '%s'", str(e))
raise exceptions.ServerError('There has been an error while updating this answers.')
From 383b5bbede8dd5b98b55494e1d24ba77ebcd32cd Mon Sep 17 00:00:00 2001
From: Stranck
Date: Mon, 13 May 2024 10:25:45 +0200
Subject: [PATCH 07/30] Better handling of canceled orders
If an order is canceled with a paid fee pretix still returns that it's paid
---
app.py | 2 +-
email_util.py | 21 +++++++++++----------
ext.py | 9 +++++++--
3 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/app.py b/app.py
index 2e51d66..f029ba2 100644
--- a/app.py
+++ b/app.py
@@ -55,7 +55,7 @@ async def handleException(request, exception):
traceback.print_exc()
if statusCode == 403:
- clear_session(r)
+ await clear_session(r)
return r
diff --git a/email_util.py b/email_util.py
index 7b0fd01..3ba60fb 100644
--- a/email_util.py
+++ b/email_util.py
@@ -80,16 +80,17 @@ async def send_unconfirm_message(room_order, orders):
issues_html += ""
for member in orders:
- plain_body = EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['plain'].format(member.name, room_order.room_name, issues_plain)
- html_body = render_email_template(EMAILS_TEXT["ROOM_UNCONFIRM_TITLE"], EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['html'].format(member.name, room_order.room_name, issues_html))
- plain_text = MIMEText(plain_body, "plain")
- html_text = MIMEText(html_body, "html")
- message = MIMEMultipart("alternative")
- message.attach(plain_text)
- message.attach(html_text)
- message['Subject'] = f'[{EMAIL_SENDER_NAME}] Your room cannot be confirmed'
- message['To'] = f"{member.name} <{member.email}>"
- memberMessages.append(message)
+ if(member.status != 'canceled'):
+ plain_body = EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['plain'].format(member.name, room_order.room_name, issues_plain)
+ html_body = render_email_template(EMAILS_TEXT["ROOM_UNCONFIRM_TITLE"], EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['html'].format(member.name, room_order.room_name, issues_html))
+ plain_text = MIMEText(plain_body, "plain")
+ html_text = MIMEText(html_body, "html")
+ message = MIMEMultipart("alternative")
+ message.attach(plain_text)
+ message.attach(html_text)
+ message['Subject'] = f'[{EMAIL_SENDER_NAME}] Your room cannot be confirmed'
+ message['To'] = f"{member.name} <{member.email}>"
+ memberMessages.append(message)
if len(memberMessages) == 0: return
diff --git a/ext.py b/ext.py
index dd36d32..1039f7d 100644
--- a/ext.py
+++ b/ext.py
@@ -20,6 +20,10 @@ class Order:
self.time = time()
self.data = data
+ if(len(self.data['positions']) == 0):
+ for fee in data['fees']:
+ if(fee['fee_type'] == "cancellation"):
+ self.data['status'] = 'c'
self.status = {'n': 'pending', 'p': 'paid', 'e': 'expired', 'c': 'canceled'}[self.data['status']]
self.secret = data['secret']
@@ -218,8 +222,9 @@ class Order:
#if ans['question'] == 40:
# del self.answers[i]['options']
# del self.answers[i]['option_identifiers']
-
- res = await pretixClient.patch(f'orderpositions/{self.position_id}/', json={'answers': self.answers}, expectedStatusCodes=None)
+
+ ans = [] if self.status == "canceled" else self.answers
+ res = await pretixClient.patch(f'orderpositions/{self.position_id}/', json={'answers': ans}, expectedStatusCodes=None)
if res.status_code != 200:
e = res.json()
From 1e6b400b2c4b337e16bedd2ddeef7d16e933c57e Mon Sep 17 00:00:00 2001
From: Stranck
Date: Mon, 13 May 2024 10:26:29 +0200
Subject: [PATCH 08/30] Automatically remove canceled orders from rooms
Quite shit code, but it works
(I haven't slept last night)
---
messages.py | 7 +++--
utils.py | 89 ++++++++++++++++++++++++++++++++++++-----------------
2 files changed, 64 insertions(+), 32 deletions(-)
diff --git a/messages.py b/messages.py
index 5664829..455f238 100644
--- a/messages.py
+++ b/messages.py
@@ -3,15 +3,16 @@ ROOM_ERROR_TYPES = {
'unpaid': "Somebody in your room has not paid for their reservation, yet.",
'type_mismatch': "A member in your room has a ticket for a different type of room capacity. This happens when users swap their room types with others, without abandoning the room.",
'daily': "Some member in your room has a Daily ticket. These tickets do not include a hotel reservation.",
- 'capacity_mismatch': "The number of people in your room mismatches your type of ticket."
+ 'capacity_mismatch': "The number of people in your room mismatches your type of ticket.",
+ 'canceled': "Someone in your room canceled his booking and it was removed from your room."
}
EMAILS_TEXT = {
"ROOM_UNCONFIRM_TITLE": "Your room got unconfirmed",
"ROOM_UNCONFIRM_TEXT": {
- 'html': "Hello {0}
We had to unconfirm your room '{1}' due to the following issues:
{2}
Please contact your room's owner or contact our support for further informations at https://furizon.net/contact/.
Thank you.
Manage booking",
+ 'html': "Hello {0}
We had to unconfirm or change your room '{1}' due to the following issues:
{2}
Please contact your room's owner or contact our support for further informations at https://furizon.net/contact/.
Thank you.
Manage booking",
- 'plain': "Hello {0}\nWe had to unconfirm your room '{1}' due to the following issues:\n{2}\nPlease contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you\n\nTo manage your booking: https://reg.furizon.net/manage/welcome"
+ 'plain': "Hello {0}\nWe had to unconfirm or change your room '{1}' due to the following issues:\n{2}\nPlease contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you\n\nTo manage your booking: https://reg.furizon.net/manage/welcome"
},
diff --git a/utils.py b/utils.py
index d82879f..2eaaf59 100644
--- a/utils.py
+++ b/utils.py
@@ -164,6 +164,17 @@ async def unconfirm_room_by_order(order, room_members=None, throw=True, request=
await p.edit_answer('room_confirmed', "False")
await p.send_answers()
+async def remove_members_from_room(order, removeMembers):
+ didSomething = False
+ for member in removeMembers:
+ if (member in order.room_members):
+ order.room_members.remove(member)
+ didSomething = True
+ if(didSomething):
+ await order.edit_answer("room_members", ','.join(order.room_members))
+ await order.send_answers()
+ return didSomething
+
async def validate_rooms(request, rooms, om):
logger.info('Validating rooms...')
if not om: om = request.app.ctx.om
@@ -171,6 +182,7 @@ async def validate_rooms(request, rooms, om):
# rooms_to_unconfirm is the room that MUST be unconfirmed, room_with_errors is a less strict set containing all rooms with kind-ish errors
rooms_to_unconfirm = []
room_with_errors = []
+ remove_members = []
# Validate rooms
for order in rooms:
@@ -195,43 +207,58 @@ async def validate_rooms(request, rooms, om):
# Get confirmed rooms that fail validation
failed_confirmed_rooms = list(filter(lambda fr: (fr[0].room_confirmed == True), rooms_to_unconfirm))
+ didSomething = False
+
if len(failed_confirmed_rooms) == 0:
logger.info('[ROOM VALIDATION] No rooms to unconfirm.')
- return
+ else:
+ didSomething = True
+ logger.info(f"[ROOM VALIDATION] Trying to unconfirm {len(failed_confirmed_rooms)} rooms...")
- logger.info(f"[ROOM VALIDATION] Trying to unconfirm {len(failed_confirmed_rooms)} rooms...")
-
- # Try unconfirming them
- for rtu in failed_confirmed_rooms:
- order = rtu[0]
- member_orders = rtu[2]
- logger.warning(f"[ROOM VALIDATION] [UNCONFIRMING] Unconfirming room {order.code}...")
-
- # Unconfirm and email users about the room
- if UNCONFIRM_ROOMS_ENABLE:
- await unconfirm_room_by_order(order, member_orders, False, None, om)
-
- logger.info(f"[ROOM VALIDATION] Sending unconfirm notice to room members...")
- sent_count = 0
- # Send unconfirm notice via email
- for rtu in failed_confirmed_rooms:
- order = rtu[0]
- member_orders = rtu[2]
- try:
+ # Try unconfirming them
+ for rtu in failed_confirmed_rooms:
+ order = rtu[0]
+ member_orders = rtu[2]
+ logger.warning(f"[ROOM VALIDATION] [UNCONFIRMING] Unconfirming room {order.code}...")
+
+ # Unconfirm and email users about the room
if UNCONFIRM_ROOMS_ENABLE:
- await send_unconfirm_message(order, member_orders)
- sent_count += len(member_orders)
- except Exception as ex:
- if EXTRA_PRINTS: logger.exception(str(ex))
- logger.info(f"[ROOM VALIDATION] Sent {sent_count} emails")
+ await unconfirm_room_by_order(order, member_orders, False, None, om)
+
+ for r in rooms_to_unconfirm:
+ order = r[0]
+ removeMembers = r[3]
+ if len(removeMembers) > 0:
+ logger.warning(f"[ROOM VALIDATION] [REMOVING] Removing members '{','.join(removeMembers)}' from room {order.code}")
+
+ if UNCONFIRM_ROOMS_ENABLE:
+ didSomething |= await remove_members_from_room(order, removeMembers)
+ if(r not in failed_confirmed_rooms): failed_confirmed_rooms.append(r)
+
+
+ if(didSomething):
+ logger.info(f"[ROOM VALIDATION] Sending unconfirm notice to room members...")
+ sent_count = 0
+ # Send unconfirm notice via email
+ for rtu in failed_confirmed_rooms:
+ order = rtu[0]
+ member_orders = rtu[2]
+ try:
+ if UNCONFIRM_ROOMS_ENABLE:
+ await send_unconfirm_message(order, member_orders)
+ sent_count += len(member_orders)
+ except Exception as ex:
+ if EXTRA_PRINTS: logger.exception(str(ex))
+ logger.info(f"[ROOM VALIDATION] Sent {sent_count} emails")
async def check_room(request, order, om=None):
room_errors = []
room_members = []
+ remove_members = []
use_cached = request == None
if not om: om = request.app.ctx.om
- if not order or not order.room_id or order.room_id != order.code: return (order, False, room_members)
+ if not order or not order.room_id or order.room_id != order.code: return (order, False, room_members, remove_members)
# This is not needed anymore you buy tickets already
#if quotas.get_left(len(order.room_members)) == 0:
@@ -249,8 +276,12 @@ async def check_room(request, order, om=None):
if res.room_id != order.code:
room_errors.append((res.code, 'room_id_mismatch'))
allOk = False
-
- if res.status != 'paid':
+
+ if res.status == 'canceled':
+ room_errors.append((res.code, 'canceled'))
+ remove_members.append(res.code)
+ allOk = False
+ elif res.status != 'paid':
room_errors.append((res.code, 'unpaid'))
if res.bed_in_room != bed_in_room:
@@ -270,4 +301,4 @@ async def check_room(request, order, om=None):
if order.room_confirmed:
allOk = False
order.set_room_errors(room_errors)
- return (order, allOk, room_members)
\ No newline at end of file
+ return (order, allOk, room_members, remove_members)
\ No newline at end of file
From 84bc07059335323537b4e78785363ccba5136be5 Mon Sep 17 00:00:00 2001
From: Stranck
Date: Mon, 13 May 2024 12:01:47 +0200
Subject: [PATCH 09/30] Added auto-confirm rooms to admin panel
Fucking untested hopefully it works
---
admin.py | 9 +++++++++
room.py | 36 ++----------------------------------
tpl/admin.html | 1 +
utils.py | 36 ++++++++++++++++++++++++++++++++++++
4 files changed, 48 insertions(+), 34 deletions(-)
diff --git a/admin.py b/admin.py
index 9c37864..7a8d384 100644
--- a/admin.py
+++ b/admin.py
@@ -54,6 +54,15 @@ async def unconfirm_room(request, code, order:Order):
await unconfirm_room_by_order(order=dOrder, throw=True, request=request)
return redirect(f'/manage/nosecount')
+@bp.get('/room/autoconfirm')
+async def autoconfirm_room(request, code, order:Order):
+ orders = request.app.ctx.om.cache.values()
+ for order in orders:
+ if(order.code == order.room_id and not order.room_confirmed and len(order.room_members) == order.room_person_no):
+ logger.info(f"Auto-Confirming room {order.room_id}")
+ await confirm_room_by_order(order, request)
+ return redirect(f'/manage/admin')
+
@bp.get('/room/delete/')
async def delete_room(request, code, order:Order):
dOrder = await get_order_by_code(request, code, throwException=True)
diff --git a/room.py b/room.py
index 6fb9dc3..a0f41c2 100644
--- a/room.py
+++ b/room.py
@@ -5,6 +5,7 @@ from ext import *
from config import headers
import os
from image_util import generate_room_preview, get_room
+from utils import confirm_room_by_order
bp = Blueprint("room", url_prefix="/manage/room")
@@ -303,40 +304,7 @@ async def confirm_room(request, order: Order, quotas: Quotas):
#if quotas.get_left(len(order.room_members)) == 0:
# raise exceptions.BadRequest("There are no more rooms of this size to reserve.")
- bed_in_room = order.bed_in_room # Variation id of the ticket for that kind of room
- room_members = []
- for m in order.room_members:
- if m == order.code:
- res = order
- else:
- res = await request.app.ctx.om.get_order(code=m)
-
- if res.room_id != order.code:
- raise exceptions.BadRequest("Please contact support: some of the members in your room are actually somewhere else")
-
- if res.status != 'paid':
- raise exceptions.BadRequest("Somebody hasn't paid.")
-
- if res.bed_in_room != bed_in_room:
- raise exceptions.BadRequest("Somebody has a ticket for a different type of room!")
-
- if res.daily:
- raise exceptions.BadRequest("Somebody in your room has a daily ticket!")
-
- room_members.append(res)
-
-
- if len(room_members) != order.room_person_no:
- raise exceptions.BadRequest("The number of people in your room mismatches your type of ticket!")
-
- for rm in room_members:
- await rm.edit_answer('room_id', order.code)
- await rm.edit_answer('room_confirmed', "True")
- await rm.edit_answer('pending_roommates', None)
- await rm.edit_answer('pending_room', None)
-
- for rm in room_members:
- await rm.send_answers()
+ confirm_room_by_order(order, request)
return redirect('/manage/welcome')
diff --git a/tpl/admin.html b/tpl/admin.html
index d234b31..98acde6 100644
--- a/tpl/admin.html
+++ b/tpl/admin.html
@@ -15,6 +15,7 @@
Manage rooms
Verify Rooms
Remind badge upload
+ Auto-confirm Rooms
{% include 'components/confirm_action_modal.html' %}
diff --git a/utils.py b/utils.py
index 2eaaf59..0876148 100644
--- a/utils.py
+++ b/utils.py
@@ -151,6 +151,42 @@ async def get_people_in_room_by_code(request, code, om=None):
await om.update_cache()
return filter(lambda rm: rm.room_id == code, om.cache.values())
+async def confirm_room_by_order(order, request):
+ bed_in_room = order.bed_in_room # Variation id of the ticket for that kind of room
+ room_members = []
+ for m in order.room_members:
+ if m == order.code:
+ res = order
+ else:
+ res = await request.app.ctx.om.get_order(code=m)
+
+ if res.room_id != order.code:
+ raise exceptions.BadRequest("Please contact support: some of the members in your room are actually somewhere else")
+
+ if res.status != 'paid':
+ raise exceptions.BadRequest("Somebody hasn't paid.")
+
+ if res.bed_in_room != bed_in_room:
+ raise exceptions.BadRequest("Somebody has a ticket for a different type of room!")
+
+ if res.daily:
+ raise exceptions.BadRequest("Somebody in your room has a daily ticket!")
+
+ room_members.append(res)
+
+
+ if len(room_members) != order.room_person_no:
+ raise exceptions.BadRequest("The number of people in your room mismatches your type of ticket!")
+
+ for rm in room_members:
+ await rm.edit_answer('room_id', order.code)
+ await rm.edit_answer('room_confirmed', "True")
+ await rm.edit_answer('pending_roommates', None)
+ await rm.edit_answer('pending_room', None)
+
+ for rm in room_members:
+ await rm.send_answers()
+
async def unconfirm_room_by_order(order, room_members=None, throw=True, request=None, om=None):
if not om: om = request.app.ctx.om
if not order.room_confirmed:
From df4f2eaf81eb74597b968811dee5fb809d7899ec Mon Sep 17 00:00:00 2001
From: Stranck
Date: Mon, 13 May 2024 13:30:21 +0200
Subject: [PATCH 10/30] Improved CSV export
---
admin.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++-
app.py | 3 +-
export.py | 87 --------------------------------------------------
ext.py | 2 +-
tpl/admin.html | 2 ++
5 files changed, 89 insertions(+), 91 deletions(-)
delete mode 100644 export.py
diff --git a/admin.py b/admin.py
index 7a8d384..3bbbd96 100644
--- a/admin.py
+++ b/admin.py
@@ -4,7 +4,10 @@ from room import unconfirm_room_by_order
from config import *
from utils import *
from ext import *
+from io import StringIO
from sanic.log import logger
+import csv
+import time
bp = Blueprint("admin", url_prefix="/manage/admin")
@@ -42,6 +45,7 @@ async def login_as(request, code, order:Order):
@bp.get('/room/verify')
async def verify_rooms(request, order:Order):
+ await clear_cache(request, order)
already_checked, success = await request.app.ctx.om.update_cache()
if not already_checked and success:
orders = filter(lambda x: x.status not in ['c', 'e'] and x.room_id == x.code, request.app.ctx.om.cache.values())
@@ -56,6 +60,7 @@ async def unconfirm_room(request, code, order:Order):
@bp.get('/room/autoconfirm')
async def autoconfirm_room(request, code, order:Order):
+ await clear_cache(request, order)
orders = request.app.ctx.om.cache.values()
for order in orders:
if(order.code == order.room_id and not order.room_confirmed and len(order.room_members) == order.room_person_no):
@@ -65,6 +70,7 @@ async def autoconfirm_room(request, code, order:Order):
@bp.get('/room/delete/')
async def delete_room(request, code, order:Order):
+ await clear_cache(request, order)
dOrder = await get_order_by_code(request, code, throwException=True)
ppl = await get_people_in_room_by_code(request, code)
@@ -84,6 +90,7 @@ async def delete_room(request, code, order:Order):
@bp.post('/room/rename/')
async def rename_room(request, code, order:Order):
+ await clear_cache(request, order)
dOrder = await get_order_by_code(request, code, throwException=True)
name = request.form.get('name')
@@ -107,4 +114,81 @@ async def propic_remind_missing(request, order:Order):
# print(f"{order.code}: prp={missingPropic} fpr={missingFursuitPropic} - {order.name}")
await send_missing_propic_message(order, missingPropic, missingFursuitPropic)
- return redirect(f'/manage/admin')
\ No newline at end of file
+ return redirect(f'/manage/admin')
+
+@bp.get('/export/export')
+async def export_export(request, order:Order):
+ await clear_cache(request, order)
+
+ data = StringIO()
+ w = csv.writer(data)
+
+ w.writerow(['Status', 'Code', 'First name', 'Last name', 'Nick', 'State', 'Card', 'Artist', 'Fursuiter', 'Sponsorhip', 'Early', 'Late', 'Daily', 'Daily days', 'Shirt', 'Room type', 'Room count', 'Room members', 'Payment', 'Price', 'Refunds', 'Staff'])
+
+ orders = request.app.ctx.om.cache.values()
+ order: Order
+ for order in orders:
+ w.writerow([
+ order.status,
+ order.code,
+ order.first_name,
+ order.last_name,
+ order.name,
+ order.country,
+ order.has_card,
+ order.is_artist,
+ order.is_fursuiter,
+ order.sponsorship,
+ order.has_early,
+ order.has_late,
+ order.daily,
+ ','.join(order.dailyDays),
+ order.shirt_size,
+ ROOM_TYPE_NAMES[order.bed_in_room] if order.bed_in_room in ROOM_TYPE_NAMES else "-",
+ len(order.room_members),
+ ','.join(order.room_members),
+ order.payment_provider,
+ order.total - order.fees,
+ order.refunds,
+ order.ans('staff_role') or 'attendee',
+ ])
+
+ data.seek(0)
+ str = data.read() #data.read().decode("UTF-8")
+ data.flush()
+ data.close()
+
+ return raw(str, status=200, headers={'Content-Disposition': f'attachment; filename="export_{int(time.time())}.csv"', "Content-Type": "text/csv; charset=UTF-8"})
+
+@bp.get('/export/hotel')
+async def export_hotel(request, order:Order):
+ await clear_cache(request, order)
+
+ data = StringIO()
+ w = csv.writer(data)
+
+ w.writerow(['Room type', 'Room name', 'Room code', 'First name', 'Last name', 'Birthday', 'Address', 'Email', 'Phone number', 'Status'])
+
+ orders = sorted(request.app.ctx.om.cache.values(), key=lambda d: (d.room_id if d.room_id != None else "~"))
+ order: Order
+ for order in orders:
+ w.writerow([
+ ROOM_TYPE_NAMES[order.bed_in_room] if order.bed_in_room in ROOM_TYPE_NAMES else "-",
+ order.room_name,
+ order.room_id,
+ order.first_name,
+ order.last_name,
+ order.birth_date,
+ order.address,
+ order.email,
+ order.phone,
+ order.status,
+ order.code
+ ])
+
+ data.seek(0)
+ str = data.read() #data.read().decode("UTF-8")
+ data.flush()
+ data.close()
+
+ return raw(str, status=200, headers={'Content-Disposition': f'attachment; filename="hotel_{int(time.time())}.csv"', "Content-Type": "text/csv; charset=UTF-8"})
\ No newline at end of file
diff --git a/app.py b/app.py
index f029ba2..51e2088 100644
--- a/app.py
+++ b/app.py
@@ -29,14 +29,13 @@ app.ext.add_dependency(Quotas, get_quotas)
from room import bp as room_bp
from propic import bp as propic_bp
from karaoke import bp as karaoke_bp
-from export import bp as export_bp
from stats import bp as stats_bp
from api import bp as api_bp
from carpooling import bp as carpooling_bp
from checkin import bp as checkin_bp
from admin import bp as admin_bp
-app.blueprint([room_bp, karaoke_bp, propic_bp, export_bp, stats_bp, api_bp, carpooling_bp, checkin_bp, admin_bp])
+app.blueprint([room_bp, karaoke_bp, propic_bp, stats_bp, api_bp, carpooling_bp, checkin_bp, admin_bp])
async def clear_session(response):
diff --git a/export.py b/export.py
deleted file mode 100644
index 99277ab..0000000
--- a/export.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from sanic.response import text
-from sanic import Blueprint, exceptions
-from ext import *
-from config import headers, ADMINS, ORGANIZER, EVENT_NAME
-
-bp = Blueprint("export", url_prefix="/manage/export")
-
-@bp.route("/export.csv")
-async def export_csv(request, order: Order):
- if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
- if not order.isAdmin(): raise exceptions.Forbidden("Birichino :)")
-
- page = 0
- orders = {}
-
- ret = 'status;code;nome;cognome;nick;nazione;tessera;artista;fursuiter;sponsorship;early;late;shirt;roomsize;roommembers;payment;price;refunds;staff\n'
-
- while 1:
- page += 1
-
- r = httpx.get(f'https://reg.furizon.net/api/v1/organizers/{ORGANIZER}/events/{EVENT_NAME}/orders/?page={page}', headers=headers)
- if r.status_code == 404: break
-
- for r in r.json()['results']:
-
- o = Order(r)
- orders[o.code] = o
-
- ret += (';'.join(map(lambda x: str(x),
- [
- o.status,
- o.code,
- o.first_name,
- o.last_name,
- o.name,
- o.country,
- o.has_card or '',
- o.is_artist or '',
- o.is_fursuiter or '',
- o.sponsorship or '',
- o.has_early or '',
- o.has_late or '',
- o.shirt_size,
- len(o.room_members),
- ','.join(o.room_members),
- o.payment_provider,
- o.total-o.fees,
- o.refunds,
- o.ans('staff_role') or 'attendee',
- ]))) + "\n"
-
- return text(ret)
-
-@bp.route("/hotel_export.csv")
-async def export_hotel_csv(request, order: Order):
- if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
- if order.code not in ['HWUC9','9YKGJ']: raise exceptions.Forbidden("Birichino :)")
-
- page = 0
- orders = {}
-
- ret = 'code;nome;cognome;datanascita;posnascita;indirizzo;mail;status\n'
-
- while 1:
- page += 1
-
- r = httpx.get(f'https://reg.furizon.net/api/v1/organizers/{ORGANIZER}/events/{EVENT_NAME}/orders/?page={page}', headers=headers)
- if r.status_code == 404: break
-
- for r in r.json()['results']:
-
- o = Order(r)
- orders[o.code] = o
-
- ret += (';'.join(map(lambda x: str(x),
- [
- o.code,
- o.first_name,
- o.last_name,
- o.birth_date,
- o.birth_location,
- o.address,
- o.email,
- o.status
- ]))) + "\n"
-
- return text(ret)
diff --git a/ext.py b/ext.py
index 1039f7d..8db2788 100644
--- a/ext.py
+++ b/ext.py
@@ -57,7 +57,7 @@ class Order:
idata = data['invoice_address']
if idata:
- self.address = f"{idata['street']} - {idata['zipcode']} {idata['city']} - {idata['country']}"
+ self.address = f"{idata['street'].strip()} - {idata['zipcode'].strip()} {idata['city'].strip()} - {idata['country'].strip()}".replace("\n", "").replace("\r", "")
self.country = idata['country']
for p in self.data['positions']:
diff --git a/tpl/admin.html b/tpl/admin.html
index 98acde6..35627d1 100644
--- a/tpl/admin.html
+++ b/tpl/admin.html
@@ -16,6 +16,8 @@
Verify Rooms
Remind badge upload
Auto-confirm Rooms
+ Export CSV
+ Export hotel CSV
{% include 'components/confirm_action_modal.html' %}
From 63c0bb75db4893c9d9182679ffe48138f136e0c8 Mon Sep 17 00:00:00 2001
From: Andrea
Date: Tue, 14 May 2024 00:25:43 +0200
Subject: [PATCH 11/30] room autofill wip
Added a button in the nosecount
[Might be removed] a new dialog
---
res/botbg2.png | Bin 1056948 -> 0 bytes
res/error_openbox.png | Bin 208962 -> 0 bytes
res/icons/book-plus.svg | 1 +
res/icons/loading.svg | 1 +
res/scripts/roomManager.js | 34 +++++++++++++++++++++++++++++-----
res/styles/admin.css | 34 ++++++++++++++++++++++++++++++++++
res/styles/navbar.css | 3 ++-
tpl/nosecount.html | 24 +++++++++++++++++++++++-
8 files changed, 90 insertions(+), 7 deletions(-)
delete mode 100644 res/botbg2.png
delete mode 100644 res/error_openbox.png
create mode 100644 res/icons/book-plus.svg
create mode 100644 res/icons/loading.svg
diff --git a/res/botbg2.png b/res/botbg2.png
deleted file mode 100644
index 632657c1362250d9aaad8523305510d11ad7fd4e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1056948
zcmV)!K#;$QP)WFU8GbZ8()Nlj2>E@cM*03ZNKL_t(|+O)mPvgAmT
zC3aNJ0Qh}GW>%F9*`&;pOA0rf-f+ub;8(M`YyQA+L77KSRc1!q>khz7l?zpS!Qmc}
zUCk&-iKqy_?r^|NP4(zGN9lk1pZ|ANRR91Hfrua?5D}y2$%^mMuf`+-@bjrzV849`@R$b
zD9XK7RY0=cD^2!YRY8)`|LR}=?9W65X1;r7<-ZWZ-Ou&Ej;~i$wy(eu{A=(2&`bV`w+-p7s)?+
z3U!SFsB4ml@bi~12!WAO!n&?VDfu-kvI3a{(Crrhb-%ns)c+3v^E_i&Y|VYWC45~I
zG$%v?rpU-?!?G;+cYpp9hyuQS`-W}X5JIT$83IFifha*W`*X1M!psOnh$;}t2+^+d
z?d=uQG~vJg*Z+)bo`z>#)(H`VSOG}=9{pSB
zuiriZVh|>%0s^hCkMm@)7eLz6-QEeo;^%d}-969x|G`itfHD}it^i=3CM-+DG|foK
zVy`NYMUbr`2F|a
z@%j0QX}KWAh%IG^C}JQ$0m%Y^f;b|^2!+MjH7f#Jd|@IGDMT`I+7aReq5xGH?v?F5
zK#}URx{K3U>>LRJsx==XHN)>$P8wJq#Y?G#5jS}&dO<;5JNeGEN4<8Y}*D^#XQeo
z=6bGOrUm=H+gTOuc{4KtGq#+t<^+gn=L-~IQvTozovePZ8t+}0II;rE3h
zDqt0e0y*2~2z(ueh`@wJGeV4D4hZv%+qUA@uixyuP((@zDJ6*5x-4rm<%Dn7EA}q-
zl^jGwzz7dxb;&3CcwWzT5pZnh{kV4s>i6Y%CWR2d1hCj%UzQ8Ly#EAM#kSdg``oYo
zVO*mjP}WHRaj5Itt|<>WZ;TQ9o^ZY00JS}1=Y;w>ai3Q~6;P>pQ#n`4y-bpaz1!zG
z{rxOCO8M>pkhlHr9;hm=*Q@6bmZz7!S)N^%j7+I4b&nrhpD-u(shTyS|?(R#!PmgO!RlzZI
z&%~d-+8&7vFkpFn_ss1*GJrTF8GXdb-SfW7tyOETc+8Ph6@)bW+Zb>3Y5pHE{(FC~
zvnWIqIq!b0<$CN~Qvsj?CIx}PzSdcROxsnY0oV_hY2A>
zyu7>u0M8@wE
z*J_CfDJDMvxr8aPXSQz}n8=b6e_uN|4P;R-sP=uYhqoo1K%Bx@VAX325Lw#+=lP5h^6!7U4L8pH~q
zai0XY*XALKv4foaT5uN}$G}K;g|&TF3##q2Etwb5x=+pm{aL|2Bz=3v%gYN8?3vGT
z!u5K^`};dSKR@yI`iga3aZ9zs3!Bv~KLk65uCLd0MG@)rYyKR7`g5UcQtmwfN)s}GC``dGQ!@tL&mpHB;va=*{d1hReD
z{r#SHNEU2+!a_^;xisuAyEX-RyP?FE{xWmU6|5&e4;A_lRO$;g>Oj^rz`6a5cuu6l
znk(zBU(@!pTCZI-)PmfAguSk}e$5pCS=qiv2mv9)E{=p+c-_VGlIx4!J=Y#&d~`qV
zbByu6sOVjhU2=s2o}2-3{od*KR&7k3?ECUZ1!@6RHf#AA1@|c`#*B5$f&S>P{%4=X
zqet)G-@^j>5CD$T05rD7AUQbPpI;6}vY++-K7C={pG#$579htSoBdgZ>$M!gSai?e
zxE|Uz(UaJyPw%ll07&}1rw;&NB7!(rO+=VIPmeKznQL6Dxnf^u1=Jib=iM85gn7Q8
z;nucKL^Gh)%q+lm&Go$=D2tH}_aPq9IM)INu)@MnTVPvD#~>iE
zp$L+JEQ%xv?2CYjhs|E&F7ePo?~4Y>mQ=@i0&~D7t8aQ+kRg;Vq9gf2ATTBe5teyD
z&Vm?c5OM9my}f=A9i6M9;VL|Fse1=2ttGqywH}r?7k#S
z#KD29U>k$2)K^Ce3AQPS13(0hz{D2F<8lGpX#j9vjO&21wW5=1uoq=049p2JfI~ot
z6TlHd3LpRs2uTGE2zH=`I3vac)#L}IK)oOU2V2V=5I|5ZfR4QjN%k%mDrh25aAXiu
z1(ez|pt~Zv50q!?p%1vgsA)*2g2v|8~($8
z_+Rl)|MY($rG$U{$A82>|MNd1P65P(1
z^HY)127bAKi17OQf|N4$eM8Qrd(k7&4djS1A~=9_-B+yZhHXpOw$*}j2zPs>?+8d1
z@7lKKP`WcYXM|}!MdwQr*mnyp8OgO_CyFRDP^RT0aXMJ_TJuw1obSkhM_3bI(Q)fxvkV=w~xq+aI*8R
z-XjE$O`&zg{cPcyoR0UO*}L$F9=8IB0+?k;fK-7HEgp)X6^=ZD-{gfiL7^QO*WvI6
z5-EWLfoK{!BW=&N^D1ztv2sYNRs5R&SW?@s<8qVF&ZTF+9HMq1Yu>$*a{d2$BuTDN_=
zYcljX%zdI>Q+Qvqddi5l?uQC_IXN#;6K~v>qmyvqulXv=X+!cv~?34HNEddAI~4{
z*tV_Heb(fn2J%%Vg*v+;55VJ(6xzq%kKOi1x$oKEtKt9!9Db$)c82&=i2&;7c8kaG
zh7L1Z+yZ&t)xLiLcv?4`53G*XK(TYlfIZ7exB|AvFC!1`Vm)j0*vxqjSRBui$BiO=
z$kyzEn`xS`EQ^7!UDx~wIF#I#U`2WiQ_U4@O(!(cw|Gt^o|_940TTyE$;r7f;j}z3
z@}i#EI_QrQraMZ;;UMnmEz>mnp-&Jg6gsIJZqFGRilClQ6uRaVfj!kOUlv0tLyTQu
zCkID;vygNfj0~w4LZAu60;1&C%ZM=`_)T1I))9>{;BtAvm$x@ZhcEc}_^ioOj5FR|
z2|xe*XZ*{*{0$042n^kv28C2RbgAHdJt}VW?>wC7~dWzn&qWbt*-wOg~
z0km|HkB4WW8yzWw^=A(S`rYpjHcw3n>P;gDqS~o}zomJ1?*xEPh
z*b@3?#DI2dAMY%rYft$)bm2-tuC1u30a5$^ueF6l=>PrxkoqoCt%S7i_^1E#pK!Tc
zFulLy|NiHH#U{S*?T2L&r-!Io^=2Uop>%
z6_YW7d3MTs>7K*E$%qi5CrX5zHhjCSxNa-;*zi5PO*biKFps&skwbMRv;eF@gFKDjG38=y6QTBS&{`wGB`vO(#6=#?z@9rJNzwiKzHUktQgoqCR=+cWe{0{
zCei~6p361Lc}U1OdTgk=~slMfy?J*;t&b$BO%IsvRx*kkzT);a4Gqpq57}
zDzHGIfMI3-P;IP~SXvk&k2`ifO}G1QkJxAmWT&wo8L0-)iJdWC2$1iM_CGO@y|Cpw
z`5_+)C@ZW>lPOlVR$V7KS(7YA%M1LyN+ZqgGu1++#CSWitT>0rz{q%%4?WwXX8;)#
z6(BvE$LDsDe2z(i81`9PM4vEDU!T=Ii))5#$I0a66$;R0o
zELr3ksPhPfc_f)UN^Bp=CTpW41kCeX+1*9{_(;Afq7H&tQ)uQ1F$Ujnij;O_g-e7Q
z$Zn;pNzDonZh*R)q(+UwQ`n>;U4edF+{cYz-AlUD!W23{2u7hRw3p?5bInQs<`JbyJWEIc(q5YRq=7XVM__ivKa6*FF-}N2#iMW>QQ`EtsT+e
z$*H(bcwJZQ+0e7n#e+BL35+C)g-v@R+z$R2EJ0t>Zk2g6%JF;u68lAZX*XDyD{t(CRrhT6#C%8K-3dCRj*mZ{;imE}pgkkZd?DUMH5~Q_J
z2Rp!^qKi+?L&=KZK+5fU#kQ{a%U}M2zy9@a*f-NwEz1HX!oF>FZReCg?0XN{3$D-#
zWigGv(wNw)Wb4oX`1ss$yM5y2Ft36@diCrdsy??TBAta*oIZD@Vn
zBPHqVJ2P67xSFhLu0=lx2yBPyvHEEeg)YISA}eUcIdkh~D0S#=?Y7@1>5r&-eXKxz
zzV{vug%++ZGbsmK7D37hluXk*P0_XTvxQt{?AwNqZ{Lto!W;u$UtcRltrS1-L^!KJ
zlNdTZU9iM}t$IzgeSNuHuq-pS+lp=7u;&COW3s3yQqG_$SW>5bH^4hsO2fTB-Y2dJ
zE+66>SIQu9G$=rw36lv*Vx4OgilI?cEn$eJcPr1v_hn?l6fFr5WH3*Fisz=*RrAQ!
zKp}LrL^Z9VB{T%)*<-BMeMex-(*kj}nu>w#M$^{2gtDFyXdUTih4fic$G+ar8Vh*9
z>T2hdK4h5Sq`TX*#p;>p=f6qynskM`&T5r8cvB)vd&X8YD^l|3rT6;Iwtt!=$oy7h
z07NB=7sG-c0yj;n82DGZmOWKi9LyuGpkr1+PPy0SS+}?>b!Un6Mbg>xTesWkGdXHe
z2X^4V<~Rpej@|hFbxkj-;G8jgR}4h9XDdw}Afd4s)KdJHuC{f(%D#VO?>cC`&Es
znQYf9jEz;O{&wl1bqlp;Zgpo)SNQZA-?QTTQM2c+xFQ2RkGWob?UQcwk@0y}!1jA$
ztgl@duuipUqL~X2
zELG+ZLqv96yw)y!0o>FJ$pY{n*LwqNrfC|e_Z4`05Rd5q(w=q3e}w*j_S#W4ih+H_
z96=65l?G)m**U9;ZM_;h$ZFOCv;}M8I=eCFY`JLUk;%YXy@Nt952;XCaMb);?4C)Z
zxcB;ve$x!*D1it1L+KC}RJk|5C=aDjAhv7|*@BvavR_yTSmwFvYqD%Z;4a;^N;N77
zm=xV#5$R8069!gQ_6#gni3RGthM#%?N>viq?x<^l?q0^)=KEUp+6rk2;%2$?zk(*_l4)
z5k;sE7(K1`>_B`*e`iOukDIf;PeElnI#`b^GY9BF`*DZNmV}`@h@>k(f)_X>zPUu3
z&cro+!@*BvI$ms!s?82QIqi292xKsFX$SbFuH({`v*gX_?ZgqJ*^5^z(vt|*b;rx=
zgn3>Nqe&>X%}tpUPRI6~S;!1p>S1af^JSi#3Os>8Rg3&+E!Cn~aOiZmLQW2bg=65;
zEx4PU!~JspHM@>hvK`(SoE}T){eaZNCTfr@eD)B%fVbYb5g*?t^;vg
z|Ht1Sb-m6yV_8g=K~S(VLO?yVpj?fr_C0`KFIbiZs)FnFio7SRw;N)Ncym_N-~RSD
zLy)jOa{1LO2F=2Hn1V;^E4s6uoau-l=bhTmBBzAi^!QXaY7aY7tRvR
zMse;FtgfgltO(aX5=<6Gl}@bPda(|ArO5MSW
zyFBVL6pzWVP}(h7%Q@9^pE)?2AvXQ5vwEIU&$VM&opqN=C-3f@>bfr9(uZhhQt|FrSR3$CWhWM44R2;Db_|Wf@W;D-O*^d(cOBL(ced@@4!9vyX{l;oA5z`dXU();s(YS`(y1TSqvxzobmd3d@-f%w
zeLrH1P(jVpk9ACGG(Tc#*)=-*gcTx_gU;FZpF|*8uAJ5UxY!v>A%;#4VtH2q!A*{0
z=dcy%o_H1Q6}(98>DSchQ-F5x7gHY#csv-b92XdQQdzXXB@y1X&E1tJ>`5xsD+DXx
zr9X_jWUo~Koib?$+|LJ(nuiNU3VujYQMN(fvi4ZxmuyuH0cWJBPHWxn9*NyppU
z7ZCjyr^3(p?blziyiA@AYq!xAOoo!{z-vFJ<5it&bC7WqU1~5m8e0%B>RO)a$nV{+
z3yNx8RdhvK>rU21L-2bjz83C{@3habI<%ru{A|l=NqDFP2d)5Boa
z+_}}M?L8vL$ZAEo-BSy+>@-k{O;0B&b(-{+ESphI!oH`@*>S-%N9(BVI~2v))v38W
zLnNE?^z~{^&@qBzL=c8*faX$gu}ML8!EIgf0brTUVLXNi=2-WEbdPw*;q@n(#HyKZ2i;hY9hr%zQ>yQ4mqef@~S
zS}M&^d#*o3Bx6!YPR=|~1O`67eZ}kRtN$I4_6_qiVUEFdSf(4U$zn}-yKZOIq~@CN
z3pFs~wqHFKO6`Q?oLpWL4K#7k=5|}_`>4RQ!o^Iaf>u1zzk8IAQ0bp3?X5dkQ>W%L
zaE9KuXE??QMfOPn3U&=6h9WnR_(?DXCt3+4TG!sT~
z7wiajBN*xYMsQDOEnPFVbLcEtWw!3S(GaO7$IK?xve=ZW2TLFGN(>JnVw$|GUJb#d
z#;ZaxF3+MEidj)9^*QD2AP!LV#8RAq)mUlj=c%~vSzMxK1v@a$qbv0>0pr3(Ie4s$
z9gA~z7^s2a$FAj(J$Ag;)^R>2?j2a79T4LMf>ic{KNpXK#Sxh~SW>4}2=7D)6>xT6AE9&2XvZGv|GrI@
z0pjG~z6h?jtFvQlIz?~_uH?RXAui8s{tj?cz7U#uB5a*$sN#tB9aAe^k(Df=Blb_^5J9vS}ia%B(OQyHC0y{UHG5$etiVcfS9)WT44S
z@N^2uy=k}oT%02E-p;0;AyZ^bcdUoX)G*fKG|iP2TJ|4PId?O}8^o#va<_6rD9i
zHQ<55fW*XUhU5$&xj~v6>QF<4A*c@6=-mHUN0TTDp7gVIAd6t;whn}cw|C|6XHB0%`~Y1?r9yqOcV*m#ioJUaca78JZBLug|?sH1X)
zq2nqKQQy0u+9AzyFf)0iD?LL#&jS0=3A!KE6+L#8PRH(W@a=Z1lxHHsdZeCt5l3jF
z#Vn!JW=a%XGwQ%k(Rms{crq!CmZT=QaZ^GFiglIR;ZI2h(;sNANkKJBAukB(w1|c-
zn^wt?`J(eDFANJNN+1Xy22kA@){tw^N_;hk??eeS15=p5aMn|yIQw<516m?tF`Zx_
z6bA#jy!)djzOSY>``#hZL@QWg?@7QEUd-L!L56+bsxJW$oywk#
ziK9~GU+R+}tZVP4t1*Ik@x56OJQ9i&;<^GuTfls0mtj2?cYdcR!*`
zpN?Rx^q+J8gTBZ(j;Jz6U8+UKaimxEb?|*wj4VYhx$RAu$YZ;xne=6~?xZ03R2IPw
zqf76*=@w(OL#VOafGs5}vos^+-KWurQy>Y**~Vk-o9zQ<0!=Yg3OpJ5U@RCgBqs>5
z(V2^)$w!B(8-T6YO-`YVZQtxWWcc~Z^(-G5dSk&+w95^+6=>)t9e5W~rOBC0>Q#0n
zx!$!T+~?i25=+X6%%dh=R4NmaKv>t!y;*F!!7|S-p=)wNMM9RQyDqOU3{hi)B;0Pd
zVeMCdtj6jG^xFz-IrW%qJuwI&(lExZAH`YT^Lu*kGwMPC`g&lzoPkYB;QDuG#1I7s
zXRRq#n<_*Q1~!N@;}{&>#L^`{<(}Bc!~j7sK$9)UWY637v{zcFj0
z<-lw{ky|!xeZDiQQ`P&{xi*g+>#HfWj>#+xHrt2AzaeEU`Ueyembf;Xee7<@8H%*B
zeTyPD)6P?;=&|)-!3k&Ppo>u>Gk=U*y~$uNlN~*xmi?;g
zAk%?uSx9|+Kk=dhr6D}^A$X+C{~o>n%uKKMSuzP&&-yb7$DRb+x`BOqR+&21k6fOn
zsRI7}NYvoKYAK|9kB>5%6b@{OW{AhzIYi8nF}Wv1-ZqPOC7!5HicxdKk1-;eN!047>r>zB7z#1L^=Ca7NU%b$M2
z^*?_gXEWZ&o4In9!m*fpu_J(ix(<-LZ*gZj(t<}ZbK!Pa7foVI$vab>Fi@rJ+%rmd
z7VV6L`kx~&rgPUxN9SN#{v2BjlV%n&I2U#ZyE8K&6zrOvDpa7tL92VL+Y04W&1|8t
zF-ie;yR8V!Sf&Y-M+$BY#;}ek3WYO7qmZaVvn7+IBOcr*p+{z|3q&2`!8M%@5R{ID
zg0Sm`lr!@6hD|c2IANM6L&b}!L`t}A<-YM?6e#^+PGnteU-xC#B@yI3L#{V>-ZdmH
z&NCL0YQEGcdxlUGGd|Z1`?l3t+?FWDTI@YK1Qdgm{9gYFJ#|d|5o1C;@lE&
zZPaNJ3LrScqu)P$*9aKjy8skkGFKzUYRuwDCZ
zH319*8!TCjwGc-g>qH1-Mufc?S&WmNabk(HXs&awo71$NGo%cnj6jy05=jMH8lW4U
z8D#8|ZM$0XA^pgn-j!MJsM$c99o-vMwNay;5#@tU*CPp))EUK(4dnV6;{%fe&GDZc
zXwjqd^1wQ>XJe8!ePwzGvgc%v(pr3cLkb;hDrxfD-NVv190ptK~V~6zP?@R_+b$-r%DcJzj
zSb4QT*4Ata-Puw1LMJmTcL&;=ZepOX50v{3@G7JE25X=ejt08_&>?=nGH3wjxPGhu
zOqI?Z$iFmtDd0{<`<#mYjAb|Ub5#G(6prz`;ZDZT&L|#??CKenZGQwt=dot{ep!}g
zs99&7he4LIEEi+x_~#U9G_$XNO*KA8H&k6Fn=W;`T_5V>QxfFt6JmhT#l6rukAYMj
zMGkzRm7MAV(76h?IjZ8c%D~alITu^v(cJI{53|mow5mO)gpZF8{PNS!b%vtkY=&K`
zNZzFlQ-C1jT
zF16m7VQqy&d6QMg%rx`
zF-a;KM(uRw$B`swhJyvn01;-_f%p2o8f;aq`|O8rp$TuB(aWjZF#RDGV}wu|Ty=XR
zH*^JEp?H@dDk7L;sMO@@uJ7HSoHNo@k)t5(*-a4Jzv)4P)88x6LML)+lBMDe6myDw
z62-TVPY@K#JmdZS4VTNC(Wb>22d+g;DPg;<`1rhaT2LNPc;<1=u3g-oP?fv*L6_)^-dKiv%1yyXRu_i)MN!9
zY=?2{9G#6sBhp&5Jkm~9t$sCeY%}3YK{g(k6*n)oQQc!W&Z<8~X&nm1@>%4ama0id
zfJnxkjI}{*9v=!tN~Q2`9jp+87qAKYCJo>v>mXZw>cv!UrjI&|MWpU~whr2G0C$J%
zFtV~U#ujs!hKP6FuOg*!>b8PC>Gfi(b801S`@Q#b
zDDl2tXDBh0N4h!(1cc7)G7Kg>b+o8^X@uZFUYS-R;?(ElubHZmnYi(c4!$m--fQV-
zi^cfW)^X_t<+fKs0Zh^`tG%50s!b|XBy5G!x2|gkfIL6@AIdbUQRQP`?>M`*cK0>6
z5sKwCX!K&j`Spal&s*M-MOrf5b?6I#%1lT(IgQ+8B*=v9J|ZH?i*y2z#@X{~;~aBg
zE4VBtyGbZ_##p^+nCakA&Bx{t8%@_u=B#UJvY-M+H%+@%lgX;prdIgf)5_0h3?IkD
zo-rbh&%VV=d29%F&^*h*u&ggc2BTvDH}8-YN~?KUbPNrTcZGR%yKU7A;fyj{iu>LG
zSPs)?@BpN!nsg-}(TZB|infmL;FNt}MyX_Pf{~9fjv$xtFhw_G=4xtZ1<0k&eCA-I
zX7?=t-smz-ZClG(d?YFXkr{1nyxeQZ52nU4_;3Irw0Z8;7`oH?KAH+r_TQOnd@o@+
zGbI~}Lv51Xnim}Afd?P!tnMd4&~Buh4zt4bPL9`#TB;r%`XKi?cPy`dRuA$v?L=^=
z9cKO4=Vmd6VNDM?-yfLS-qYB>J7wxbuKR-|_YMR*zn2f0o|Vm;swATa+snG`M@*LQ
zRQ_CiR!Xj6EbhriEOTA6(==6S*}m_%-L@`Yv1U%aUz%&32w%ih`JrCq+kueMhIPFHKYc-*?B)b!WOrm{B5d0Y
z>$&>-q_mj1bMdCyhA)U+%KGBmuq6Qxq45fY0?;eBOFz2-vuHtmp^Wyv&NPx;b
zpr;P=Jv}kh&N3keD|Yh1@TRP*bzSQwDL-2rB9mZnR4vm4d{NXxOACDpMr%|rN({}F
zPQPV`^PY|B8>bS0131p+86yG?)}c*Xx5auq5~M`iX7Z0kWcjnCf>cpVd~B+yOC#y-|_nZiLJ!G^Y45bD)2V
zUk9D@am^pcb{+MJR>+OlKhh9s=yb?orgGEk`yrnFSX6-u4?Fl-7wHlB*x$eA-*EsC
zhSs7uFxg@-D-Rv6VT@4+yB{@LN(Ls9wqk_nzSY_b$UzIqy#p~<
z8A_Z2<~df)@^-s*#h=sR$rx4Eyf9*Up%{n4fN>gOFL5|hHwU-hE-r|VZ&ROhtmM5%
zk0(vfn}*JL-+dtxpBEfzOfTcstnhEsJm_@nWYdYa_qGv={hZx`_E%Fl3ataLT-j;H
zyfFhX@^s7dDX4jb)Ix+cJ
zHFjnek20AMk7JK?udWKs`rt5LTO^X>qpZ5Ui8A@0L(T>bjzPSJ?JGDh3`;yo|;1LH0t2
z`_(^^ToZ#l=x`PH1^3ZohkLj9yBJ9w7)Jv;&otrT@o1?-ZgT34)BVcsY+qP1h3%Ph
z#>>k~%_#t2PYIu&p95|F%ul1&-H#>HN$y-(4l=xNW9sF*bCo-`>T%9^|5_HUz)~sZ
zV+dI0rN*-|_HbFuT(eB5speQt=bh#SS+aTWbk}LI+9eaxwBg6%t8WH9Is^AzyOECV
zJ^Hbok7&(3NYVowg|^=}#(IDt=M3(iabLuiy~5N1J4L3EclRG+2QfQIMwxyP$2fny
zK7JO<^zm4+XY{(0_y`eqQsM#Hz4pd*%%Bxv$t!rh9Bog%B!x?cbt-(>upM
zIvd*kcmVvChKyifn8zV84{
zwY%T5ZkRhZb}@FblzS2J|8#4PO_
zQoiBsb-}NH+Yu-OBG}Dk1c3;bWyUg3m8$dkxmLXrvDW0MPb7^xD^*|m3{^hfo1@`P
zG5Vv4cQiXFNujEK_cM=0S$q9sC*7m5LVxHefNi_p0SG8f
z64IXB{5;x7H}B-wXv)17Xn`3qHpz;05q-Z1VGdmV4;nA112f?2xc!f1D;6K@pO
zXn>yxG^(McY9K-__T7Vq9n9#vqt#^3=Wq8^-9HNcJ)%xN>RO!@dH1nLPV<=ieXTC{
z7(tmj4vSj{wNAn*g+Cb+r5Nu)sLsk3YSJ9)yOo{0ei;jE^1aaI799&BFy?u-*GnG!
zJA%s-34(pQB1ALI4KX5R_!+Z7Vr*yQoy>`<>DoCE$@dcA2lu>zYjB|F$q2_TmLLC<
z2hPnk7w9@<)*-)D^R#K6v27c!*DF*B(^U4_edEH`NFTTL&i9}{kC>Y{@6DKP$ZdwXDs10D%!=tZb69UrWckud{k~uZ_t~6}KcZ=1Kw5t0$N@6wA++
zZhIqN>uK|n!w(kd$1isrzg7z>N8`=CPU;M|s$qgcLkX_Mpv7#cNH93w-gj-it4AYy
zL+gSvlX2ILr3--L@^Ya^~$quWJj}HK5UPD6l25vqld7fu1
zmy1z2p$HH_GL|VqGRI5p-<0F*
z?VjFMJN8dD;5{{KG;w(}zU^%M!9cA2PJ+}I;l10efdk{{mYO^eV~UfTme8OFi811G
zxeVW_B$W0Z!RUid^=rOyGnNrmEIQIFl3ya$;6P9uqCIFceMW`3e5>2Os6BJ
ztlKFd2m+BAEY|t5O;2Uck!FI3NOh*SZE(f9?GRb(hPTm6ge|3d_=@YXG&H#Qk8xavbyWB(YS*
z!*1PiKI9?Z+RUOE>@2R{0*7*sBd6%&bp#I8=(bSY
z%S4+PBWU(BDMlb=r_V-XAW$h*oIZ?HXA3jK3Iq7~w3MOQBqMu8;x;?z_d;-q(bn*dVEb?DO0vyqJu$GUbS3+mA~OoZh!Rr8Ph)oClj
z$ZG;fu%{LCJR{`|x38bLt{(`*c)837Bv__^kL?DC>G&?w1+$NkNc)D*k5BB|4oL|q
z@0i|}fo?-SQ+d>j0sXjzmUujKu*Oqb*I6R-+?+w{xTm4BbX?f1&(ZIbF8VP^J0j4M
z1(CUGdU`X4d7cqm1Jr0*&44JUgyprltR6>pkUOXM8{pAFa*x7~2f(=bxoS*DlN+P>
zmJU**82VTXHX|l;g)U=F7@1;o5mxx*18rtuOrRJMqao?Ln~fFPZbG9P_Z@|y!rR$vk088x$(B?AhSKY9%9ncBudPNA68zwTq6LK+0%ij58JN)NhehpKc
z+%(565VKTZ()Mq5rU`>%1OwRC)u(reyZw&i$Qp`IHOcIs)lq+Xmb}%D;)xS@PrE*P
zuGIPA2fCi^)v~wrKxa2vehBX9t#}Y)sIK#(F*o|V0RcvD=dL*SdqQm8ayK9Za|KnZ
zw81r`Wt3kC%_OuX@LES_7PLNu$$xjoO*I5F+Fr^S7y&(C+}y9zsSA6#&jpsQ=1quS5YR9Rh*66i{?Rt+C?ehExVPFaNMd!X3P_Rt1DKzkKSI{2sf8HFFmD8gC=4m5mVvs?KXeU=;R;eI*ta}+2z(%2J`4(
zd(;8#X(`iW2)=5>zFF72+-lRap=n{e8MsCNW^-e;I@vx@$
zT=4n%iR;HFf{&M2bFT9i_ie)z1J>>8^hryAKd&Fq55XKK{POdc>Uy5iRyR(ZA`l{O
zw;MjbeZ#)3o|pv$O!a%32GX>RX7JRxELU*Xj48^j%mQ@i2`Z){$Qb8H4~A_i5Bf56
z?w&^$#6Vf2o{D|qtXoD0X?vbpko7{NKG1lMLFd@*2Nc`Bd&i$K&y&vpjFpycg?6fX
zHV$0T!EN0Tr&t*fy?(S6+USx_bTi#C=tWD%i^#^(lyP&dJ4!W~jE8Ym%@mPGYEch%
z6uMtQr~H|iCZ3?o$Z0dmA$xJ3-PKf#N|&?GQ{{juPHx^}iFk;u7+|(J$bG_j?{$Fc
zs9NTFw!*jrYDDjdyXc@Z`e6q99}T|LQ8n+4RYn6et(qO*BwnPqZ_ZACFXno+_@GW#
z^qoYXYf*mhc+WpznsPiWdUnQ9Pt{Y(^Z}Y&$2&cu>5|+(fr^AT34s=UrklTs2FPv(
zW04kf2cH<6Gxj7FC*8z}IbfP6#1MT9U13z2@zw71_p-(Wz%Acqu%VzfY&^@
zu@f~}3Dt9ty0KmDcYAEyb*1~)#KsgUObDfFkXNyAe(5)Of+ZrCR(8
zD?-SP)fCGJN|SA+v?FOobYLLH+3)KPfk4E};p|#1L0*>G*!b#-CBUk3IvX7!}F5TrO=MdCu5Gz&?(N7}(QR
z{SX2LAPAVIDofeRBo1jZgE>whVqCX`+vgQTifIafW_)~p;&!_t{VbkfM!dbf+ZmaR
z*|6U>B-cN4VB3Gof-udPB4Yr~Krz3X&7AbN-@oFozy6LrD{gysdi+@t&>4$%)T4G^
zoNj-hG!3W8cP4rC1(SO`001BWNkl=ca!{jyzWvBO$vPm=)DylGgQTtp%R^u+^JJqYQ-XMsUt|VX_eP3-t`{9Dx%s5{
zc)$LJUm&*$#
zQG8ynShqB|$RA}!h1o_Ajk#zjOnOkHHa*CggMEXf6im~>s82-d`BgiSPC+SvS#cgm
zjGeSaip46RirGzpmubTL`y1X~Ul2mTy56v@S7cuC_WBdv-@jm{Mge!8&NO?Qd
zHE{2h$tFd)Q-AiRT$@(a``=egwB=)}cN1L(gUmm`#5)~hOWMY)b$hb_}*QQ$MHgEgNetMo4NCs{5dhAxpI9>CiRQrg|Ewv9Pi*A=f*z&y=Z
zUM|+H6vhAhpZ*g7!gbs5al4tB#-5-;^&v3@D5dT`_HzRXffOw64oS@|w-mcJ5~bSs*M}%LkYkY!saR_ZZQ@7!&)`S?$(>NNf>k5!}Z!C$^(OAb9W^bl&HYdBF
zqCK6IyY*$GPb?8Eo8nkia@T#DX><-P31H3{Ns=d$a!QGf^wzZ3(h})1$GMj5MJMJ7
z{GKvakq-12JHFH(3`Y$QGqP(NLub$jck)-!(=MzWT}O}1l)HZ$pcU65>xqW0P?n@k
z8bC{g07alQe&*qI&yiDVCPYUs3&~Fo5Irr<>wCzPSVfO@SC-i5eP`VF7$ZFSG6I$K
zgkh8)P_o<}**L5+1DQA?2|1NG*O7VT
zF6&^aBfG5I-MW9wro%3Vn9Zr(%;9pT8plnV#1;OD;eu&e5M!w0?Fga93)6rfay8|F
z?KHA@=`iSpiM{peHa%K5*)O`#fWm2=I`Gyy2L
zQG0d5fXgKqKr(L{^@5oozRU>`HVSM_&L*pCps}>dIU7Q(qkW1?yisyr@b>nG%jJUG
z?S`*kzag(5C~cm3o-r*8rfIRs7a;t;1Hb<5ulW4DnuH>l-+{OZS)almM{OP_#Ps)W
zcs#>7W$yK5ZuDu>h0mBsb}6!Nh#?F`Ug=C3Sjc|P8u(Pe+9FHqn?%8ZT@f)YGGX{W
zwNMlVq-V$-3V-aixGJ655~~7}fu5Z?+`ugK&s=@vTk+W;_sEdknHiR@zu)JZ3M3mN
zL$gU9mf8FSOm^Z@!qSN*Mzsc{Ul($k&Mgo#c~p6sE?xBbE?L>>12Q2?IRwg
zK14v?^PLQ{H;LQxemB;tvLf=>H7*^`ZQE>e6JcP%h$?bQ2FhGsY|LKX!6Yydq!~QF
zEsUSvU-A0(1;mVXw;XiacI;U|Jb@V2U4OlRNU^RT)}Z_CUvT}r8K4~_mgNOM|NINS
ze6jbs-ER1ozy1whzkOocO{&OlJe+jHw(ky{n51hLn~K3=@&)Qxw|#T8?mO4O4>QO4
z-mtFiApvr#BOt35+s7Sm+ij>>?hIy6z?a(XooFy_?ak4kaR8E=bkuywvXAp_Ku?2V
zCGA6(xd2}Z1xWzZ&PFXcSD*^aObB(xJ6uL&Z85=@w=c*!)yg(s!_+ebVXEEZPU2HC1*%Bt@!8l6TkoV
z8^TZaML%vg=Kv=N0b!m%lB>SXx-SRXLA368P8mrAVVZE;c6|N%6`L=(P29a$%_*DI
zHkcqIOnh#fLU5xOt%=#7&-0N>eUV+d!&$^MlNDJ$>9MN}l%?;X)y+|zz0-7c?*MiAxa`#Snnb)v*|2#c8S|HI7C@d{*o~(IU~E
ztM_w5527(?>FI;VCD!_{_<^w)^h8%gPxVlx5IK`-i2Qvzrvs4OyDK~e4f=6)4M=!Q
z$2`u%J>Ie!?B!e=*2b>=aq5@?h$k+{BiJItAx@xOAsx2v0F0^5mvURuoOs1Qm}>HR0!b3Zn!dpmR&t$_!FyWeT+pd~eBkTHif_Ah+C@=8p^_WTZWLJAb=~BbQ*GxD
zQenr@f#$>H1fjOcgM3HAGZAF)fBPm-~Jiip#~w
zJ7o>H_X5M^K7C<66kI-+e46IwBjEl5Q!Jz4T6a}y^G52JQ8Q~ckhC;Aa!#;5M`H-8
zkBt=(Xc^ndjG*rG!3@>V_PjHF2GGnKGxVc!EstmF0K-vk*z1Ck50apM-HAP`vZo4b
zve$0+j58*JI+&p%JGULm~Cw~3)
zD}MWGKxf`}Y$-Vy25ehu_pz`jRjV=N88}%RICm1#k%4&Es5ls)_264SJ>g6~a^}=t
z*?uYyC0%E3>V-k2q}|jRY79@`KkCgn_Nh^I&xzMeMJ`sxYO6IRIR;49GPtk}1S5ii
zv8PlgaH%NveFHF8--a?ps-HB`H&@Qihn_uE&WWk>h_J>Z4LM{>6Wt3)&47@J9~bCn
z8fHGZsB$3p1zbNit0G#%T)oa@gbBsgu6LjwzjXG3QSTD`N*i!D)qUS_ypZ^4A
z#_hIO%0dXiHg&c+zZ7`Tw)9#fkad4#T`>`=iO9C?Hg3q>_DZ5+6lSX9RBFepnxM2r
z8L8{IcjI1i&fqZJ=_t$f_vSUn;`LY@pB1>rVzN1(T4L6l^&OLx9wq7znr+|BDVXTH
z$<1(xmacdi!_c`T9`)aM?(4;vzym|p49$ulh5pU%xb6j1e@_7G^wH>eNuGSFcU6aq
zsszn$p6VIXXkI8B#%_noH7-%GnQ
zv@p`B=$NW+fSs>pJ~t1vUn8^_cnM@HPEh8{mK7$4SX2y%$P-C^Dgim3cW2=3_cZ8d
zMg7qQ{8=adEYa;dME5jaJsnq?QpQ^6m;2f0UqaAY}zW
z5h1#yPzl?*W8HRqUT>J^89)F0GhU{E<>d{R5RpZ2y{`E7@qy3l6
zd@BwN)GImLsR3n2>%UtMVwB+m(hQ&v5lGF5uTRoT0UScWG|wQ{!1ji?g_6(G01a_T
zPsu5==PS(>rODMoh@H&T^KBw)M^Q8X8}b>&AO~V*P?5HJJfIU<7k$UD@z_-_-}87j
z1vg-}A(7))EFm8D-8hHchf1F0xdzI(50rz5Z?6~p>6f4J{`Lk9;`8Gx(so6R5$|sc{^1|~2Q141DB*Tn@!M~|;dZ-W+itjAA}%kl`1z+-
zEK9`e>x5r_{T07``|4e5YwVS#k*GqGc*ExLEN?kDz&``-kL$QHd>+KRGp6RlTzqJ{
zjKEv(gQw7QK(6A$2hR`(C_scITHM;V%_Ir)jL2-BOt2gR&h8~2t!hn4H=iw_o1e1+
z@jsgx>f~|5ch;8Xw+rjtt|P&TLRx?Q1u1|~Hfw42HV8aG-gPIJy+ZK!^qI5IQVJ-L?UL>GDJ{`7y>Q^9ent#i|%
z2b;PtJ_qwV4~SHx3*EIHJL<>98V*HcKi=q3QCYhcWdsd-r_N`cCvhV-PhL@GgZBYP
z9O+ZsA%I!bcEEiReqN-v
zS+{bU3AMxezHq$n4F5qxlA^bM2pSMN->89k@&A8;el|bufx$~%)AA4?>E$OqRjlAb
z^aRLgKnR_8B{nyh$ev~+H0)(+Y0ZFQ=o9Z)F?vS^#1-6y7{W2~sg
zhpofWI<>^nlCs<+J)+Wes77Cu5;M=}$h}dSb=Dc*K5nNJ=8{mSoNSaDbCt@72=?`c
zb&~h;?88${GBYE>9S)e;_m>+=ZTR;62j1Qu
zD9xf1DXSC*+%t~>b@AVQ_%1XLtb3z8w)AN5+k}vL9>iYVk4w7jaR)vn$53bWOh&NG
zTI`B>H3-yGrq?05Ce^Sn>>IX+$pvcIf?GE=3YyyavoNW51rtUsQl+&TnYmlaUZ%zX|p`_t}
z+}MT@)Maj0(TG>T&`~mSOgC{!L^70=4e6hoOFVUTCs2=;yjnNx_fYgcy}aCUzunMk
z!P}2FY>$VYV`4R+QRJHdnU$ivuA17kZx2Y_@bc$B;q_G|CAZrR|Mg#g!>|AK3%-B*
zVexzw`{NrPk9XYfFL-%<#pi$d3%-82m0i@t{ACFPrVTPNCC1jrIJ=IGEFj!8YfAjdvUjwWE3HSQibHg2b*T2{BsH&vT4!nNbGNC)i}(;x4b)otnH~F@
zn4l0PK(nmIe3LBwLm6a!d=aFa`hN1yi2jd&lLgpq6Iya%NdlmsIDI!G7
zprVwXQi{?!YsJ!17(jRokf9@OWE^rz$cE0kp0!CVROUwPNCiJ4F`=NZE14SWBcZR5>Auh+0e!H_?R@x9m|yVW>h@XDIpf9=y?#*?fgQD1c9eZ}@E0
zXy_ijvzMw!UdaA*luhTA76#Wujq9u__V;uw?38+`A#mcFLLuZd$3wQcI#$WlkWyX7Ojj3NR`@bn(rSf_}RUlraNnE)k{}J=(Dmv>KGJpL0
zcrHcCaPQ8g^?9l6gAYORY+4Z(eN}))Q>Pnl3-rxG;t0^y-QQ5b;;b-&|lZc
zbJ>mr+U_Mo42NuUFNQH>7Gx4L*Gl`u)0;m};6SXk=xkQp*CHYV^bC+N2T~u}VBr~~
zz9VtEIR^YCM}kZ|0<4C+3ksaMLuglny@t-20?mpL`6k
z94P4h|C_1k6IsaVtUfYunbAt^!<^%@0RW3-l6N#Y-apK7sngzY(ezJW)7(6C2eEpe
zNUQkUx8JaDyLpp{^#(GfO|`lqB|_OBXuII^rx*O?FMr0DFP}BBqg8zW{tdtV_BZ_a
z@!f{6Nf`jbc>FM<#(l%*FQ4)9a>wna9un{G8-D%eH@rP|kSIWxI0Jl+_`3qxo-rej
z-#7Mu13g(zHDL>kwYbk60oSAKbbvlh0ZM7zkhxVk(c9&MO$X882}v)Sdekd3t9jq2
zPnz6hX-c2&cih(-zJLFL$M(Scwi%UOO;16D+8JTg<*a^YzMJO^pwgba+>jUbrFd*R
z_Pxycfc#WjN>#xWAfyJQ{agnQV1kv`=WlP3)7-?C!TZ@!GZ>4Yh0W_TPVp5aE|!
zf7OX)SZE!=4jbRT6*Vy`1?%laoBPC&S}^BD4;C~GjX^H>AW!NT&_4Uq^mU^}HJ7
z9GmH9bn&a772T-iNk+Hffnn-Op-KN^+WnEbI(?2_@Eq^Ioci{Ua)|%ew&vkEqCi6K
z`5A*AcBX#O^8*os7Ro0|CtVKe#yurGvp)3+|1&phvb^tcy~6?Zr13c3r}+Fj>te|X
zYz;4;Z`5X4_5-pMBr)
z{rh)p+m4slFa0_18SJ&%IYQkO4glWXG}S|sK^RqHQFugX1}r`D#3`(QFNU@GB0~p=
z8ED+&S~OlMg=VkJ`#)jIr&nJ9nwSVW0OrwPyS_9sKq#=>51RHd^plg^gy>pE<22zW
z?ziEC?%JL8MOb5|SPkT6G{#7NPfXvgp8q35>=~N=N9-n-yv153uB+qEJmxN6f#}F8aGLXnTY#JIj
zG&4ghHnhtrK{z9^nU%U;7gckf&GQB!?c`XGX-uB2(MSRLpgE6Hy(=p1Rc7<_^@fN>
zAZKKUs~M=ec_SE2>du!uZNg1IjsC36M>4R2r?b7+LWeznLePAGq4No2>bYhdlT-Qe
zEasFZ)@0PV=0y$4YAqvW(|vQ;XyBfsEPC&l6PQw$tt8Iqi4NgRY96!NpJ(b+el20=
z2E7}^hBN#l1pQ|iZ?VZ6sX#Fjkozz&GBnT6xHj|cvi3?SZBwTu16r1xuq@ihH8ZQM
zO^`7BQospJK-+gL>x$YMmL=i8{kOm1&wu_C-rj!T+qXB1`%T#Q&1$-eZGQ(f!phm$
zP=dGb@7T6olTR|^U;gPYfH2l&!N30Ne_-1+^fXy+`!W9WnGE@==6EI%jouLv@EPWr
zGkZ7o8z0GLodMzOjgcf`b)?HoaQ6KWp?-#nv*T=kVgP^nhd<+fzjxH!P081F#cf@%
zEPujd+wkM<2j1S^v6mgy4JcPuY=v5gPV)|}1_`c;mj7ZQ6khk~#R*HC
z61tfX(#_!i2ljnKDO;EjagP{_I(2_&ecIU!#p?evJ;J{4N|7gll!_|q%$L`U_x&BO
zpFRO1cz=J#KmYSTietR6!0o;%?m=QwL5W^QaUPsrF%IRNr`Emz(usS
zDV49w$2lE-Zp9Qgb(qHYYv=dDK&_M*OUhwDG%}vs#r@5o;(oX~0_Y|ZbuXvf
z%V&q)adPT~`&CBh#|#?!P;zatsTubYkc6HpvDaZJuNS}}LZvTk(9{=50e$K9l@-{#|^x|o?U|Cn>
zlvEpOg*kD(`>Ubb9!WYqdrj|&G%zlhitMhp@1am#J1^$j>O+BfpJe
zRq5KCVM>;gNjm$eH-c!30Z
ze?sliUJ4ww@Vxgyi;OeCO?e&3BtVDy2a~vC)Adxoa4>(1^`xFnwKwK$7lVnAmj&D7
zF}e2paKmerA_oQ|Gn|CBQr7r2TPwwk1QRRM5~8M+WK7ah8d}+r6JuQx-rwJlneg@N
zpYi$gC+uazFTea}f30OfXDoO!00}^qbuh#kOhDasl)5V$6$StJkN=D>pI-1+1|V9$
zOKnrsFAiYU725rgH`TP4`A`RQ&D%_JrH?hwY*)w>`xpT@kIan5$dOsyWn@M7h~toI
zFxkrV{+DIJr%#{o`uYhs*0b;3hUbcSXFT5DwNby_@adDW5)$F}Z*>5VCNXi}9amme
zyC+Llx_hk!`|dTyO{1Ten?{b-irv_9n(i_rgzt^Uh
zGlMvzzJa@Dc(_S7(|6iMMKhXdvl)m@Nt?beZP*NTVZuYy)Zt}aK~PuY+q$BbqA?VP
zXm_zI001BWNkl+aGu}kx{+TF>+*Nho%#vGE-E^CUp(c
z^z#xW5E4q!7(3kzvgIKA^e*Fmd(|eP4OGCh#(iz3l#x@m;ZQ)slDhB;DP_H345T_e
z-q3ajKqcs;G_FOn9Fw_Nwp#m!j2O{Fqu?Po!m!4rS+-_#^o7iTg{i;(Fl4bqy;Eod
zvmnr}JEhCxZ5d%mLA^ok8q5!V!!zF9}k|3}G^QUNQ&k7#CuNgE3@!
z(P26xPBy6}dO`)mbkI%oMb?7q6bBdBKub=2&~v7oJ@X_8W>ATzgEQxZlrw6hZg5gV
zte$cv-)+&1;K%l^4ScKJBf9tE1OZVOOOlMxznA5Y#%(L)3r@;3my{X98_nf
zxUo(jKG|?zO}o-SW<+D9zJB?l!)e=(6YIdsh*l#nSw<1%*CeltAcYpW}}D2^pgbGG&N|$b(
zMWdg;ZJTOyt>(x?$OL5HXGPQ*T{RG3M_uiDlNeZSgE!}hVNQ4=1S#Q0b7&S<}U~0NggtIe8%?T*|*wmK@x#jn*ic$2F)(NVY98$vkB(vt{KjH9+3SVec;1$tmoqMOx~acM{btU
ztRyxcCyQ53AS|3fMNPbjhECnsP$K
z-nF^k9$h|rZ0uVDs!#_CKY#v=$kl>#hx@sX3?8;xlT~0TZ$syf!c>t!ND>wIL;=
z#@7a3^tsVzV0MsSmUUdCVk{+-mn>Pe^6CsnoO=&2d&EO2*{1^=W!*&GI9~OyVlo==fp>RWm(1H91%WVW6uaw{~0qMT>2&?sBNy=uA}HYfo1s9~b7AE#u`kc2@Wm($l>A(2PAV6}U2
zoCqEftK(~n2yVCAK-@rG>zLD`8R)f6cVY3Sg`w7yhj^t7g0vwbh-xgk0rIq1hB)T2Fp
z51;q3^ED;R5rQh!n)jb%#ZO)`nDSHzbtI75WS-Mnnx!m|P8BDi=^SzWx)x6La=3Za
z&iT)d+3MpA8PASJ&fw&q(RI;OJ9EM1Ykp={3GARlBd?TX8wPj7t+3CpYe%;|Chr+S
zcNLpf%P=3!DR;S+OY35j*Uf&oO|?OM{``yGb82Xoz3%a>iWJy6j&c|U60#wQ)a7iQG7r+a$UbY%8Mk%qpuzWjAKL9F23@^Y
zIP9^G2w&~JSNr9JvR>$nN5-|N(bK608Vouxf4J`_GiMz%^gfY3isr&~WSs3&Mr_*{
zE?~sL5s)Y$n=}Z9)azQ%STx9BF6jQH@`Pp?^vQsNMhxN#@%-g7QYbt=k}M{eI3cA4
zjqEx%MTw=6>2!xghRsOVJqDbm8KrDd76roJZP0kUw|q1Y^r4z*hZ)K+s6`Dt%U(d4
z4oO9rn(j$D*;@DG7-%Q@Ae(G&cs!KRTMe1ep{(?Q1OQ|tldbCaG-=rP2Of_NWXu;a
z)=8~ehd1(u86Z>}_AorbR78x0vw)My60!kii4CZjDb!Pa5{Jqg1PY(^>t
z^~+a${Yv=j-~OgK_1?ppeA!JQ*%L*0NGg~TDheLkL(yzwlaMh>I&0e^u;ck#%A>Pl
z@AsE}{UQT7FN@LT8LjRpwc)ldSl4XKDe3DWf0TLmhq}!$fj?5@{if;0(?B^row!kG
z3XWRr7=?93ZP=S>3Lt28
z4~GT%fjc6*+{Msc1IAKI)tLX9(`Z@pBH!#z*&~o#?ff8m@!O`f&sB;-rT7
zv}ZZ9N~1bIbR!!!orFOJfyV$@MLoAZ5R-**7oG$xyb
zAM;#mwTyJMK13ptnJv+qb3PP1=e6cPBUzIuZ8%ZeB|ITe{Xox4M@vg7ozj2QWV+eS
zQ7RK9N%atkIrf9r4acqq?sbzuS*OWVPklYoWbiq8qX;esje{98>nxpB)mrOpu1-Ne
zJtODuzcQ&W4%GA
z>*3>(K791YAY&Vq5o{+v
zqzhwBKhHR4tob$u2O@avT7T`OVp&{Tv8a1PpvXh;)Vxk(pW^HCkch!p1ueU}do=5m
zRqNeEsp`(C==9}SLjP_~`+BKq5$MqR?z(Oqy
zP6eB^Amcu@K#KIK%_s@ex8EK3ei9@G!2@!CJ9aeeCW84p>by}4n~zN_}*cDqfc
zLR}va5Z%YNVJ`(`-?3Fg0O|r+l`)VR1mH;{+0ImX!drsIUkGWXt1;71v#e)y+0l-&kW=y$9M_IL@+;T
z)i%3B#M?*j@gp-bqWSY4HO0@TmJ;-cf8X~X(TXcnG&X@B9hTQl#UJR%PnLM{;Vkxe
zt)6K^=_Bm@J8Q*&hxkracIo1em0E8tY=N03?jGvp=Y4bK-0**
zNu)Elh{U}0JSBxew1fe83z08ZCX?60ytu}yF%0tZd|kxG)|;rVc$h>}Qg5WA{&C;;LqyM&8IrCKkIi#5*9$0r-Xb%{3&NxS
z%JKO#V{dLaFKh%6{A8GMUr8pZZDTur9&++B3uhy
z%ieux^qMnjE!ZA=FDx??iWTimxvsr=_w_Jn=DJ49zGLshCwWq{(T6&7!nGT3eiIr#XNKzy1EZ*68;emXtL~3BdN)kZ-vURE`5F-$P^L>YC`*8iZ9U
z+gRfzDFe%rvEEilE6M^Y6^#T-Qi}iWc0*394xrbJZ3EU@#_hhKmW=J84PDOqOr)%m
zV2{CU3hOF!K?I41t+6zM?1EB6{fFvqwLSNws)}=x%@siKGd?sU0pTn|xau2D1KRK`
zUp>=T-Z0Tf@wi{c`iq&c=7c3BNUhi(57b>7=p|>s7(LV4GY2TE&rz2C8{tA3Wurg6XZw6XdqbAx{6oPCA79y4f<=+9LipEt*2n%{a>Ufk%e
zkg#*}Bu}QU0i8{3&9c(e>)x=L3;n(9*sI<=cLnsRv54T?w{L3MFZR8r5oTgkJKU(%
zd3x8R+wPzN9K^iT%qg_i9zA_U$-I&X3~KgSc5o!Bc-HLIuZZS7dx&F!8`0e>^Q#G=
zxoA|b=!Qo`LEZ4zj><&T{>n@uqB|{gg6xM#w#?35_1VJaQy?%ZlDjDjD9yUyW@xO$
zfs^?e5Rp>R2w-8|?4JxDE+giQ{>H>pv+dXRBV(j1H1uaolzs+5KQ`{C=KcSJYdX;f
z4zC{#1rAG?Y~ve2Fr!FiPUck+X2@dap;s
zqBp1Gb1_=mPenyU>ShFw2bzmva4;Q;^6}VZ7=nH5di^YF3Zi=QB`Mxg_lg=L7wzBO
z;fh32b~QT+g<3$7Mm!gH7kA(5{Lg$a(~E0(582R>QWjm3_1ekQl#izNFt~J7gwJN3
zj<8P-{r%_hM89)-vQyh5ZF=k%FipB6^mBaa(FrzZ*VUI(lPV(E_s5CuT&-Z4F2v`pQ{h9=KS#pS6qQp->FeTHmyC7v1WBCO0_sbXcsPR$s9yu7>^vPWj{TDmbUT@1jIY4vrTp7qG_ync?R^2GSN
zYS5pvhZsjw&p|I2JobIa^y2W7K(M$rnpySt
zYIE6}bMh!#Mo)53gWh4xnK^gHXp`(mXUbrDyU?}
zv}hM`uHnDKtZJR*cazV`wRG)g5Ojam8Nt;$oME->eX!Ds(#mh^io9e9pUrY-mJCm<
zyD6=x)-FAw9>u3l|E(2cf2k*mkRY))dmFazKXALPSaZV5dc$qWcq5=}@1UHJYlcV}
z(O|@=MWX~M5n7R+LIP2=yfs284N{8vv^0~jpyTFrkhmS$Y|j`b&j7PO`uii>>Ddr6
zo&ob2PLsi7tO1?=e$}tVv(f=nBC>Vdkk*^s*ETVO6uYs0KmfK_NvYrtM1t$@v_E^)&4c*pzW0kP-p?eTtMT1Dohuk~6RzJ33WYN-1C
zen)wG$J^UG0N~4~Pp0oDWTUv(eaF64?6n{tDVSF@$K(J1qXzvd3T&^%4oWXN)!ovm
z+kY>DenssXh0=KVYoN0Ed(?pYACNC!r^&ki+uPe4_Ewcbsuh%(t;kfY2fo|bCs|
zPKefVqdxi)c*K#%6CBsqk=A-b@(zxZtzFQbp;1r5(kHh4y4XFObKx&CpnVX*&nJji
z`Q)P^Og*XK=sgg%mB90?%b(SF{e1|!e9+F(qysTd+0}VMxju4Q_9!!qpl6BB
z(zS&mt@Y-;)e3PL!=YG=4f^p~_O%M0WFj*vr;fKn1BK8qMY*L<%{VYj>%X=?av|(*0PlxGD
zH(;%NX+d7}d-uAX=H(ViWFQ*XTEkm7iD#Rj{EtZ>%{
z{`m1@Iya^~-@`fUKPxh7u3~7#@^-J%H$2{pr!^KL!E8V2=Z_B*o1)eXfLrUUu)A!#
zncbL>mf*i(dnczA32ehr
z%~TdjFwa9Zxnk-*7i=|9L+yiSm1ZF%e4tDIq7RWAB`ODL+~0$s_va9F+DB7jyu7Gx
zcS+eyAElq0JIYh939_WABOSX-R+IiYvw1s_pn9?r3G!lbn6}?n5tQxS{8lP*En54b
zK+upA;pOFqx5tKk-;nnmrEY3km$J!G8>AI&-py=}Z7*1}qQykoEasH-nIM4KkaW<*
zB$>Q5E^5uAKg4A--l>Pz6!%tJ$o=Mv{p}2nj=>+95}eo^ENd=sE)
z-2yhV*Q7?AoKhbo=n!z}gNV`uo3Xtd_$dOc_Z!|f^)OhM1xwC`(%A@9~7ynD`ad$kB!?g+xb{9UFkZ#s8MYQTai}J2s$W6j-L!{jfR4kkPsWXQ
zrB108zm{L|>C-1LGv43ddqJ?|jI!I5s8h|PH~Z?o9j=2HRRQ(sIj1}(;9`**LL4@x
zf%w>yFcp|{vrA8>W*!xC)cYNqm19A2h2RdO5FNJER;SNOp&5Z8oR`x!%S2{(rS@jn
zi;VEOV>DlkTsvyP{k`q57&A5(Y#L}z6@l2p1e@%^O^9G|eKJps`+N5Iyx=kLN9Kr^
z2gX4&f28`vs1Ra4X`G);pnn!!|5*h6W2#{~Y5dL<_ABn29Mn_Ki?7o=%RUW
z#}?b4-M4D~Kw@5-FmVSWG_{pZEFRobOv-8zJvD<_?QIUuSxl3b+YcDJa{f6z6q8
zVlvvXSl0WXypuKOi3z3CP&Imltx$JqQFT{dch?T@f18
zzE1*$*oP>NU&s@|NttjJ9py%>(ts8}0}>+IK)
z!CWk@1t-J1BW;pHiUeB}Po$?$tO?V^`?@*~NsbIHHq$V=7<78fY)b7?CN{l49*G0Z
zQdWE&GXaZ5vsut|pq*<0=xGkVkErn%ApxLT&vgEvWB0hT
z0*WeC(^W=y_-v^St$T5p{KN|7zW=2JvN*!7LFHtFz-QhV@-*fV7{-BD3_lj7;!jOV
zV6^14nnyit%AGTJ!8GC$i{b29kZaAT*PG|%fco#sw#Qi9}uZ3?}2Dj
z#=}gHNqsrQ%tntAmnNbJy8#0!CnRQU*iE)4fMlz`$#{)-ZxR
zacS;Fu9<=W({dix^dUpL>`OSxCxS;zAEvGgc1l(cy6;Y~4tMiR&B=>>a`rq2S9ysJmxxs2-voL^gltiG2}z-kS(8WKuA44)_oqn
z|9Hca6JB5Lc%%oaxd;*%6@u)sBuejlA&iM`|rDn_8mn{Ia>yL
zLIF@8?`TyYWN8gjTQ>!v<9h(c>o9f7H1((hkH->#>Vz`(fdjdr9HaHvyL8A+Z+%hp
zp$h1djeIN~hUrl5+B`4#BK7NNmgPG#nG}I|OxUVz_OUpXkw?^<`2K)$0!g!I2hohd
zeFuoJE-TjC+8Y^PYrQ}{_KZHMA5OYG1eW6GP*(JwaE)=plY}
zUxHmXAAfd_W*Qp2n3?=D2*~U#FJa;u+}e9`!$2=3=+Ltv5afT~hOv%R=}6R3W7S`a
z#vVW&NU@B1)ZXyV#_)tD+^76ex1Lf0s7}tq?tviDmE9i6)lpb(|%zwTAUc1
z2({_|?@S{K>j2!rAUZgUOW?Y9r`O;(jF6o@IpErTzZw9s#k*Bexe%DukZ8%shF(Fc
zCtzul=TU!bCRvHG%B3R86tC;LrimUN_o>!m^DD^NQDtDa7`qlsuBv#W((v-KPDuvj
zR6rtBp-YOo-=9+UP~6zQQ7iUPFnNrfm&VR2V-@|{s%
zn>NLh67e()_J~S2WuKH$=J-NqBni-}ASJ@O-jHv)R`#-6vEI59wz^r5O#7TH8m@Y5
z;n6Dvle8q(Kd1j6BlyItLw$DmLP|$obBf;=0z+NWnYg?8HmFIZpeZ71jdB%2x@3@z
zlPT;_V}ENfqdiZcU`>$`K&XMcv4b)bUW3O2xKssCxxTh3p$L3%-7SMM;`c@%HwxL9-6AGppvE8tc83rfa0^DEkJfn?`}<
z1(X+!a%%!9&R|-QmW;+#*-oXPiZYJK+`vJCQg*us##$&AAN$0M=#LDbpAEf^>v}ZP
zJcgQ_CP`jv-cb6`!}lWY>;gY4kuKPhkpSjE8NaEDxd!vP95$dPi?vHRb%q|>zR{Z2
z5Uo*XHtWp8faH{I_(-ne)CM^h{Z0ER^*9Rn?Vy
z|3j_zdW|hESqJZ>ON4UH$jhyN{=|%BxncY91}2wXjpT+dcgYJt4XHL{6pw7NOjsAjPQbpX1`bkCN*(bQBB4=9
zBV0Z-*aR<(>@ZCUt*HUPl5T3!P)N-e%#^J+?E!IssNMY*_2;!hwhgkbb{MJ-f{c2R
zU>5zQLXg~Ppfd~HxFszfWKW>1S$e8>QZ$4E1<^I3ouY4UsxU327YMa!Mj$NiBU@zL
zZfHd7Uw!>6PRH-}JMQ=Uq^onv;8|Cvh8C3BGSoHSwFork!}D3KvGB&jsXVQzd#ctg
z6h4<;3$hIC)*8OQzvF%1alhS=a_&WtPK@XHm2Bve8t)w$0IvV1u2G&hDt%hvn-;BfOs*d8$n~&IJJ|(UZ<`LPK9#RuQ=t4CY`Gr;29!q0|<&VXKy^)7>0Cb
zb{kx^#M3~_3jsZ7*)qq+Av-AEu#DGP-0UVq4ckcvI1L>dLOh!rZdox-d@@by6ImL_
zxf`0wtEfP@zq$qEy3s0|fm(b3D(>qWc%B8jUQvLZTi$(HTJUfr}rq
zK5&wz)G$HQz11jReaIt2d#TqECuxf|`bqP53$WK6d2jt7S|BLf4|PCoy8orXoLSe@
z3)8*s*!C?Tt_hSgmS7$xw)R!lkT2OOSGwh+tszfr33Ghyjhu8-o@VaN=uCZPeO?`f0
z6zgVISGEtHQ5h|^xf_!06Wy}x#pt*hiGhNKl*Gtd)%NKIVv9nuwU9DF+z~+cT2^u}L99cAP8_JzzL)I#pb|4p`%G;*XrMIMifpV1^gL>rsKOo1
z=Y;ik$FeN={{6dpQ`vqlCZWqqwt=G7HBNc=NH8sImx<0Qq8ZWa!lm*LNtt;Li#m>x)b}sQ2~a01ve}
zhRL6Yb;*NiVS^7ci4tT)80w$757e3f1$)0I+E@4hg|Y1oly)E`%^k0Vy%i**XiKx9
zoR7yjLCeaq*QT^+v~Lk4SQ8G$LdHi^n|d{dWFRMvqpBDNcacFN3EjY|1crp0B-f_$
z!fNRoB7$w(bZs$#64`lYYpLppA_U|hL?r0fPs`zi(vopJKk5qr-RY)q`X=dy$)#_>+4n1py%Z_
z4HWy(TN*g60Fr?Wp_W8KZH$~3l)Z-Yn$T#0@C|i;=vU?yoSIVHshjX=YY!q`*hr@i|iFS~DIaY24stqV=Vr&Fr+AgTAYwoe@-zx|%TSd5Eg}o>~6N>@Y_5
zyn(KC%n=L2>3Q%x=A(I;Oj*Ek;lw)YvZvQ3OmTj*XTm_p=LCSGVP8A>BZ%0j&EE6b
z*wE;$*$SCpf+f<>Dv*{>XHG~9qovtcGa6UX2yzWgNU>*EqXF4auEeSDb+y*+
z4nD1gcwepH^e8s5?9?VYy_b{*1v?l75fPH)p%*r~xkd~PXLJ|^wAO}FDpIM)iEzsc
za@Mt6YVD>fDJAf2#rAmM{rv~_UL+632HTJi=6DLrT_qB5!c&vDXXZ7o(~YQlM6^{h
z!F@0`qWx?}%EtcbQy!VkOQ$|gucPkG0q?z#g;6}q8qlbY=hM6fy3EFhxlQ%4tw=8@
zd=l6Ev)9(q#pCniw0*Ln<8i3jhZIKb@3JK(9hOS%gYu3P)Yh}ar8P68w}2oYJa}W;
zni;HCIH$~$723fcWioj@u)t<{R=Y47qe=HoX2>KoC!a|BNP>ge=*VcRI&5u|VPVv^
zcda81J}xm*mD5u?>M4Tp;6?F_Y2atWY~VZI-@2qANwcQik>p;tHE@E)KP4@WQ)ZOC
zb((H`&W7YhD`5!#GbX;
zDAe~1AIwD)F8c!uK*DIkIxzK-TZq#F8tT5|0n$N8&PZ5L8w}(Tv;ySireKIgIHeFj
z+$;xO0i|qG46Ds+h4V}~Cot(bvhCYtjWaz{P_O;2lj2p%erD+7JT{g6}49U_S!3!+WJU6{8GoLT@PP+S7?NEjG_}sk9jw!MV{2f#b<;>Q=i96M&jwRVUmnJIb=~5Q&~WF9&(=bX8~;Fsgi2hqA3z9d<~yTHcr>7*bvLqiW%q_8KkoFrKBj
z%%{4zbqSUCTRoc{V)CuP5G9%7f%(8-84?>~J5TX97Y~_F0hk%6`_OtqEJ460DkJ8?
zDFaK+SeImt23Z5hMsdqXc}Gf&?NNrYL#HGT9pg#c(jENh@?iJam>5^_OtX88^&0RY
z|8(Z_xmX;AbvhAk$VRtW*EE^!wKmgCnv}7Lv8qr~Vx>K}@m)3I5z*u{Tw6Kh>5?_y
zE7nyJb7_KYkIwZO&72U|b)5{`dJbWrb%ET46gIt9wE81zO>EF-s_LwbkG=*`%NXhQ
zlgZp6gK^wTo(#dRwAoLyzc0S$SWrE44Sx3hu_3*Rn~0_#r`SD_({(t$_OonnL@j7&
z(mS=1OncWUY5{R}S;dDesvOl4+G{N(z1Ky;5f|7Z7V&gZ-^
z00g?r>Uq#Gv@2{9i<}awp~M{I8&_s;M6DaVMRYQnVY#zxclsrhLPUlqg5i&?ac#mo
zOgf%Znk6aX{usnyGetpd#)YAP_E~Ydha?50Zm!rhrhdTKa;PGZG7Q2rw|hlS?w$)Y
zFt+y{+qUDe=@3E(x8{Oc+N85bkkZ6`<76^((V$;NvVEl2oBbJv%*!csVs6Vm)a&T7
z?T?gF_iJ!NK{T8qN2j%M%&!fyi5@)}*Z_hUQz@NpJbnINNXT@EcQdXZd8
zmtnC8lGqxf+1wzeZH)EM0`lCt>n^52sLZr+(m_GHIKzh=pq>&w1`VI5;e0$Z90XCM4Y}7}Lvm!zSbc6_dbPge8NDtGHVJ!xNDx-d=Vl6na5pjRJ{BSY
zEE#BbXPAX%KAUkOjpM
zKvGtVUw?!&jbTFBx>Ph?dz$tt!H_0QldoYuZCAs#L<~;A`~CnEVa*H@LAJf4){0+#{S}-N?zfw=OJ85rjE@NK+lDFy
zpYES=Tkg2Ot{p7cOTqWIA9y@A@S>)exoREma@|@B7D{N6P1Xyn>kB@Ae#O@>{{o_f
z_s5347LDc-HLr^jfUC3Y+1d6pj_!OiRvxCD?vy_@>AjaWDVD#_R^jq;?eVg%H~Vw+
zqU}H(W-!2agv1$4sT;_Sbwih|;P`wxb$O##q@>zH
ziwDUm;kGJz-b6Id&L<0^NI7%Dnlj$ghR3Fq$JU_J#nLR=Y_^-C=z!EOX6n&uFbZjO
zj6XJkJr*f!!m$vn=E(QV(fHZIKN{iqw0PgOAk8^h@yeY7AC0HT^hr+ph-N%QWJWFp
zt!fFBQkpX4Q{rxbbGzMovz>DWfl*5B2bbsa>VqlZM4bX38}!y1_HAg&$fntD=0(&d
zS5G=1=P^3wgo5)N(RTcrm{%JMfuj-OOnVK4s;BpmFA#Nl8bxMx*qI9$%APc(luId{OwcZ3{Mq~#BFOvou&l({z3Btua8irNZ
zksHq@#wQf=BG3HU;N)nWb{ePu+)Y{EI!fV#mc_nVbPUD3Cl94ZcuPf_MxRWV7dp?%fZ^SEbJ>f
z;AxO_bg_*nPek*d{0v>kN`=))ISaPu>%#R_C~;7&PP#xw_$
zOErRusC*)zbiL|17Spw#%}-RBhh@#(v73OJMU{#dOJi72TU9oinR&4f90G%*JIWJa
z0npk^C;jZ)Wx>+X{5V|k|L)rs&eC>>gB)gK$0F)kqyEu=HgqtMR?WOq)DOaaPQaSd
zV(cU_1_N4!6j9pd1yibh8%UJ$0i+9jyYZKojE70YeucRp*pQ2Tue$&_aT&K
z(|g31Q>0su-_tsX`d}!YWs$iP<3eljRdCh;A)@qRX$pfO=z8WcAe0_0M`KRh8+YzT
zuR&JW=D09EG}&=&r2NOA%Q@Ef!i(-;zap&xCO>Hw&0Fh^ysSu^y8NvPlFuu=+cui|!{2J<6Ifi4@<)jTcG4gFu#w`eY+425(beV#q@J%YX>9C~M
zg0eR-sa(~4UushyjraElUbceUZN;ZgpK-h0vEF~++mGMy>C0#21pMvWH~jY7-#|oo
zxh=TgUqMKyLiqmnJNB|G(!Vsti`pcKE`26`ZUS3}Ccr^U=m%rlQ|N2)Q
zQ2R3wP}h$d@&DOB^F__n2Yh+yb5uGawq*Km9ul0|%p-*e%bh{71@|Uh0y8bh8Urm-
zU)K!7J!DSk?4cCDkGehCFmRw5x^HUQW-MlX=SDBc2`~4%Zeq;Xn?T;*vA5D44r@Tr@IldamG69Pe2#gmQI{7{
zS`OK#*C@GM7Kq2DPv1kGkb~B=@ph_fTm-&x^OB89XN~n^3&vrr)Ki%SbYyv%%aZZ>
z`huJ_hi~5>sD&m2t*JpZQ>s!59*;+F(0y$%Z79;P@737Q)_}CuC!SYy_DBJ|EEeM=
z{Gzx%=}C_cwiguA+9&>W>0}R3N2rd7#7x7#o=l$2$K;aYe)YXqDEINY9UJ$LrlBXw
z|3$159o={0e@zvAw8Y^+KZaa_O06GoOIAe3hbVJqX70$CgvcMCrL4<3NdjE9
zG9pS+N3(IDH9OT*`gCIGsF`H=RUB!>mWvz*LgP6klL_IVpB<=$R1
zqm8xIgA9bwxJ_n09j$9>_J-ip9*YT$n;GdL6bvYP6cq^4Ogp~hULdLgkmk;~JNPqn
z_}Kt-$XnzvV5*k`ygwSS4~o1FM-ozy(@`H^>UrMAKv9}fncHkQ8WTKv!h{UD)U(0C
zv-kW_oMvp~;u-7?!d(|E(Ip>KO3L|X$t8GpxeKG5z%xKAFTL2oj&(rP~UxE
zj*^gQ3r7E?Fw5e$VM4(6J({mdsu&!*hd
z9;j8cviGLBN$bPuc&`R~Li)WpGY9JwNh(4eh3CxOtWnt+)t)l}So$!QXyje{&qFg=
zDjL8Uxl2i2JWY%5+rz2g4z
zg8SDGpw{^vz|MV6A^pF3D*OxmeXOt$W?K?Oph!j+*NSX2S
za>Kgbu`YMqZm-z4f?xjQH~iaw{tdtX{yQG~4$h0FZ?p<#Wy`wh?X$?-pQX7^U(*Cd
z%4HO-M>C!$QRw}ln_xQYuQRk}XnS5Sp(a5v4Ro-zpM_1-P*3bMu9j587oV*&&ICvT
z6zgA`d&vML+aZN
z>Za6hrC0Cv;P0hQ)QF*TALz8}r9BKssoJ$~Do4ujshMx>a>I^V-)EEdkMgOdacB^o
z?&EHRn$xLBdRBypStl^f%$+D{0aO*KsCA(F |