#!/usr/bin/env python3 """ BSN Chatbot — Multi-Channel Intake System ========================================== WhatsApp webhook receiver + admin dashboard + knowledge query API. Architecture: WhatsApp → Meta Cloud API → POST /whatsapp/webhook → SQLite Admin: GET /admin → filterable table of all submissions Query: POST /query → RAG-style answer from knowledge table Usage: source .env && python3 app.py """ import json import os import re import sqlite3 import sys from datetime import datetime from pathlib import Path import requests # type: ignore from flask import Flask, request, jsonify, g # type: ignore # ── Config ────────────────────────────────────────────────────────── BASE_DIR = Path(__file__).parent # Load .env file if it exists (for tokens that can't be passed via env) _ENV_FILE = BASE_DIR / ".env" if _ENV_FILE.exists(): with open(_ENV_FILE) as _f: for _line in _f: _line = _line.strip() if _line and not _line.startswith("#") and "=" in _line: _key, _, _val = _line.partition("=") _key = _key.strip() _val = _val.strip().strip('"').strip("'") if _key not in os.environ or not os.environ[_key]: os.environ[_key] = _val DB_PATH = os.environ.get("DATABASE_PATH", str(BASE_DIR / "bsn_intake.db")) VERIFY_TOKEN = os.environ.get("VERIFY_TOKEN", "bsn_verify_2026") META_TOKEN = os.environ.get("META_TOKEN", "") PHONE_NUMBER_ID = os.environ.get("META_PHONE_NUMBER_ID", "1152708791258718") PORT = int(os.environ.get("PORT", "5002")) MEDIA_DIR = BASE_DIR / "media" MEDIA_DIR.mkdir(exist_ok=True) # Brettspiel News logo (base64-encoded JPEG, 993x217) LOGO_B64 = """iVBORw0KGgoAAAANSUhEUgAAAfQAAABvCAYAAAAaADK+AADyaUlEQVR4nOz9d3hc1bU+AL97iqarN8uWLdsS7pYNNhhMMxBDqIEU4AYChAABbgIJoSXUEEpoIQmQQk8IBEIxoSTGEBKKDcY27r1ItizZ6jOapmnr+0N6t/YZydY4ud/z/e79vJ/nPBpNOWeXtVd919pKRHCwHWwH2/BNRJBOpwEASikopWCz2ZBIJAAAeXl5AIBkMgmHwwGllP5tJpNBIpGAw+GAw+HQ33M6nft9ZiaTgc1m2+/n5nPYT7OP0WgULpcLdrsdAJBKpXQfACAej8PlcunfAEA0GoXX6wUApNNpiAgcDgcymQyi0Sj8fj8ymQwymQwA6M9sNpvl+4lEAiICpRQcDodlLEP13ew/58jlciGVSiGVSsHpdKKlpUWi0SgikQiam5sRDofR0dGBvXv3oq2tDe3t7eju7kY0GsXq1atx+OGH47333lMcC+fBfH2wHWz/F5o6KNAPtoNt343C6EBbb28vkskkAMDv9w/5nXQ6bRFwwwm3bEG9v0ZBG4vF4PP5kMlk0NXVJSUlJQoAEokE8vLykEqlAPQJ5FQqhXA4LIWFhQroE+rJZFIKCgoU75nJZGC326GUQiQSgd1uh9vtRiaTQXd3txQXF6tkMgm73a6FMdCnRLBPmUwG6XQa6XQa4XBY4vE4enp60NnZiY6ODnR2dqKnpwe9vb3o6urC2rVr4XA48MMf/hAffvgh7rvvPmQyGbjdboRCIQCAzWbT9zdbUVERvF4vli5diqqqKnVQiB9s/5fbQYF+sB1s+2m5CvREIqEtblrq2S2ZTGrrfV/foReAVq7b7QbQJwTZF1OgU2ngd5RSsNvtWmjxc3oCRAS9vb36vmyhUAg+nw92u32QIAf6FINMJgOfz6fHyzEEg0GJRqNwOp3o7e1FS0sL/H4/li9fjkgkgkgkgo6ODm09d3V1IRgMIh6Po7m5GZlMBqlUSs/PvlpJSQk6OjpQWFiIdDqNGTNmYNy4cSgoKEBFRQXKy8tRXFyMkpISFBcXIxAI4Hvf+x4WLVqETz75BIcddpiiMuF0Og9a6Afb/7nmGP4rB9vB9v+/bV/CXES0RaiUQl5enhZwFFDZLmWn02lxsafTaSQSCdhsNtjtdu2mN93ybNlud1rL+3PZ83Na4SKCeDwOj8cDANizZ49UVlaqVCoFn88Hm82Gzs5O8fv9yuv1qjVr1khbWxvy8vIQCoXQ3d2N5uZmbN68GXa7HTfddBPGjBmjPv30U/zqV7/CBx98AKfTqa3mQCCAnp6eQeNwOBzayq+pqYHH40FJSQkqKysxYsQIVFZWori4GF6vFxMmTMA///lP3HLLLYjH4wD6lJRIJILf/e53mDhxospeo3Q6rcMhZ5xxhvz9739HY2MjDjvsMK3QOJ1O7W042A62/yvtoEA/2A62HBotZJvNpi3k/QmDbAtcRCxXOp1GJpPRwtX8DoW16R2ggOLz+be3txcAtEJgNsacHQ4HkskkRAQejwexWAzxeFwqKytVIpHAxo0b5bnnnsOSJUuQTqexZ88e2b17txZ6xAiYzeVyYf78+Xj00Ufll7/8pe6z2+3GxRdfrOPldXV18Hg8KCgo0NZzUVER/H4/XC4XGAIYao5EBKFQSK644gr1/e9/X4A+pSgSiQAA1qxZg3Hjxum54nxxbgBgwoQJSKVSWL58Oc466yzY7XatHB0U5gfb/7V2UKAfbAdbDs10d5stkUhoS5xxXLpy8/LyLNaizWbTAjsbNMfQF7+3rz4kk0kNPlNK6Rg1rX0AWpjm5eXpMIBpydvtdng8HgUATz75pNx1113Ys2cPgL54fyAQwJQpU1BTU6Nj5IFAADU1NRg7diy2bduGW265BZdddhmUUjpWnkgkcNxxx+GZZ55RXV1dUlRUpFKplLbGzXHsK9SXLdALCwuVUgrTpk3Dli1btHKilMK//vUvfOMb3wAwWOFiq6qqgs/nw+rVqy3rYs75wXaw/V9pBwX6wXawDdNMMBobY+Zut1sLCMZm2Xp6euDxeNDb2wubzQa3261d6t3d3RKJRJBIJBCLxdDd3Y2Ojg50dHSgu7sb/KyrqwterxeFhYUoKipCeXk5Ro4cierqapSUlKienh4d+7bb7UilUtql7HA4LP3p7u4Wu92uAoEANm/eLLfccgteffVV7SkIBAK48sorce211+p7eL1exXh7NBpFJpPBxo0b5Z577kFHR4dl7CKCDz74AMFgUPLz81U0GoVSyoLU5/eouPT29lpwAaYXgvMdDAblpJNOwpo1awAAxcXF6OrqwsaNG8FnZF+8T01Njaqrq5OGhgbEYjHJy8tTptfjYDvY/i+1gwL9YDvYcmhk/nRBRyIRiUaj2LhxI5LJJLZu3Ypt27Zh1KhROO200zB58mQVCAQAAB9//LG8/fbb2LhxI2KxGHp7e9Hd3Y1QKIRQKKSta6Z4mY3CjRYo0Gdhl5aWoqioSI499ljMnDkTxx13HCZMmKAYnzYR36lUCul0GoWFhUpE8Oyzz8pDDz2EtWvXIj8/H6FQCDabDa2trVi/fj28Xq8qLCzUvzWBan6/H2PHjkVJSQni8TgSiQS8Xq8WrMlkEqFQCA6HAz6fT1vTHAPHRC+C3W4fNGbTgk8kEigoKFCFhYXC73d2dgIAGhsbNX7BTMkzUwudTicqKirw+eefo7OzEz6fD/Qa7C8d8GA72P43toMC/X9hM2OGZKjA4PxiWkZmLNbMkc7OcWZcl1YksH+UNz+jtUoENN+ni5P9NWOW/E52H7Jdp9k53XRp0wqjC9rhcEBEkEql9HzwGalUSse84/G4RnhHIhH4fD7Le/F4XIPSmIvd2toqK1aswNKlS7Fp0ybs3LkTO3bsQHNzM/Ly8pBIJLSASCaTmDZtGiZPnoz29nZ5+OGH8fDDD+u5N5HiTqcTlZWVCAQCKC8vR1VVFSorK1FSUgK/34+8vDy4XC4kk0nE43GEw2G0trZiy5YtWLt2LRobG/HUU08hnU4jEAhgzpw5cv755+P0009HWVmZXjTOYXd3t1x33XV45plntMAMhUKWuPS6des0LTD1zOPxIJFIwOPxIBqNoqSkRI0dO1aamppgs9kQjUb1GkWjUaxYsQKnnnoq0uk0HA6HznMnzbpcLmQyGcRiMXg8HiilBrnh+Zphg6OPPlqvP9v27dvR1NQkVVVVinPKfqTTaaRSKeTl5WH+/PlYuHAhtm7dilGjRmkl4qCFnlsz14Z7laEj7m/uyezwCgDL/uceNZU57neCSRmqyv6caZ68fzqdRjwe1/upt7cXLpdL/x1qHNm1IdgvM2sjGo1qugT69g/7YmaPkEea/WH9BfN3QwFag8GgdHd3o7u7W9dP2Lx5M8477zxMmTJFmf05kHZQoP8/3ihgTUI0CcQUvETwmkU8sjeQ6YLN3iRmupP5fNNKNPOJGcM1Ed7xeFyDr3gvc9PwL8FgRGCbblJzfGa6FS0vc5O4XC5L+pGJNI/FYkgkElJUVKRCoRDy8/PhdrsHMQKTSeXl5cFms2HTpk3y8ccf45VXXsGyZcvQ3t6un51Op1FaWooTTjgBBQUFmDx5MsrLy/Hxxx/jL3/5C+LxOD766CN56KGHsHDhQg1cc7vdePTRR/GVr3wFkUgEVVVVimM315gAMzJMszALWywWQywWky+++ALvvPMOFi5ciI8//hiLFi1CZWUlLr30UrnyyisxcuRIlUgkZN26dfjmN7+JhoYGeDwejRg3LX8ARLLL6NGjFRmp2Se2Qw45BB999JH+30T9b9u2TaewEaxHq5xjtNlsWlEw5yD7NdDHKCsqKizC2u/3IxwOo6GhAWPGjLHQMWmJ4xozZgwAYMuWLTjhhBP0njqYtpZ7yxZKJih0KNyHKfCzay2YPMhUvkx8AwsYpdNpKSwsVPF4HL29vVJQUKCSySRSqRQ8Ho+usdDT0yM+n08BAzzDzLYgXdDjlB2OSqfT6O7uFo/Ho0yDoLe3V/MEs5l8icLXbrcjFAohLy9Ph9dsNht27dolq1atwtq1a7F27Vps2LABLS0tiMfj6OrqAgAUFBQgGAzCZrPh1ltvtYA8D6QdzEP/X9RMRm9aO4lEwqKRDlddLJVKaas0GxV8oM1k2gRtOZ1OrVUnk0lEo1FLTrOpfZtCDBhgHNzk5lgzmQx6e3slFAqhvb0doVBIVwjr6upCKBTSryORCHp6etDS0oKPP/4Y48aNU1Rs2C8KCBFBJBKB3+/HJ598InfccQeWL1+uN1tFRQUOPfRQHHXUUZg2bRrGjBmDqqoqlJSUKHo1bDYbtm/fLjNnzsSoUaMQjUbR3NwMoK+4ST+yHG+99RZOO+00tWfPHikpKVGcq2yktin4OBcAtNLFeTKtkRUrVsiTTz6JBQsWoKurC8XFxbjhhhuQTCZxxx13aCucjWMHBgrLuFwuvPHGGzj55JMVn8n14Lx5vV48+eSTQlAc70VBee655+LFF19UsVhMx/LNsfX29iKTycDlclmUKf41LUJWoBMR1NTUyO7duwH0MdRkMol7770XN910k2IVPiL7uU8cDgc2bdokM2bMwHnnnYdnn31WkUbpITjYDqyZGRjZ9GqunUkT/B33H40DCnTSifk9NvIqEUFnZ6eYmRG7du2S6urqYZlXJBKBUkp779jXYDAoHo9HZVv0iURCe5govJPJpO6rqRTSqIhEIsjPzwcAvP3227J69Wq88MILaG5u1mEizktpaSmqqqowceJE2O12+Hw+PPHEEzjnnHPw6quv/tuuo4MW+v+iti8EdLYw5neYqkS3JZmow+EYVL2MgonN3JSmdcz3+Ey6uEjcpqWel5cHp9MJU5gnEgn09PQINduenh5EIhF0dXVpUFhHR4cWyh0dHejp6UF7e7sW4oxDs/AKrU02p9OpAVdutxtr165FUVGReDwe5Xa70dvbawGwsYratddei2effRbpdBrFxcX41re+hbPPPhszZsxAdXW1SiaTFm29s7NTAoGAIoMaMWIEAGDr1q3w+/0oLi5GZ2enVgwA4PPPP8fJJ5+MoqIiZaaTmYq1CQwjczMFP2PatDgIhps0aZJ6/PHHcdddd8nDDz+Me+65BzfffDNisZgFFc/5olA0Fave3l5s3LgRJ598ss5j7+3t1cyWY58xY4ZmzLwP0CeAV6xYob9Lpkjm7XA4tHJAb002HZv01dPTA4fDAa/Xi6lTp6K5uVl7fFwuF5YtW2b5TTYwMZ1OY8yYMaqkpERWrlyp19t83sG27xaLxbQSmZ1COVTLDq2Z3zPDf9neQK5JPB5HNBqV4uJiSzojsyZKSkq0Yv7CCy/I008/jbvvvluOPPJI1dbWJmVlZUpE0NPTg/z8fMTjcV3sKR6Po7W1VQg6tdls6Orqwt69eyUYDKK5uRl79+7F3r170d7ejpKSEjzxxBMoLS1ViUTCki3CvjscDgSDQQkEAiqTycijjz6Kp59+Ghs2bEA8HofdbseIESNw9NFHY+7cuZg+fTpGjRqF8vJyFBUVaYU+FArJZ599hnXr1iESicDtdv9b3qODAv1/QRvKi2IyI5bwjMfjUEppV7LpUnQ6nUgkEpo5U0Om9URmOxSTM+Pa2YVMIpEIQqGQcMOHQiG0tLRgx44d6OrqwowZM7BhwwZ8+OGH6Orq0orDjh07EAwG0dnZaWHGQ421P80Kfr8flZWVKC8vR3l5OfLz8zFixAgUFxejrKwMfr8fBQUFqKysRCaTQVNTE0455RQ0NjbizDPP1LW8qY23t7eL1+tVH374oVx11VXYsWMHampqcOmll+LKK6+Ez+dTZkW1VCoF0+osLCxUZFj9jE/V1tbK1q1bkUwmEQ6H4fV6tQD1+Xz49NNPNe6B1uRQc82/9CQQ7Ga6tskYs5WvkpISdffddzO1TN566y0Eg0FNB+l0GnRbmrXXeY/NmzfrvpAesmu1V1dXo6CgAF1dXYOY9s6dO9HZ2SlFRUUqFovpueO8m8zKjBOaVjr/5ufna5qZOnUqPvvsM4RCIT2nBBrS25R9LyL4a2trsXbtWnR3d4vb7ba4Zg+2fbfhPBgmjVAxNTEuprVNdzf3uync6UJ3u91wu92KSh/xFx6PR7W1tUlRUZESEdx4443yu9/9Dj09PXj++edx5JFH6poGSimNiWF54LfffluefvpprFy5Ej6fD9FoFG1tbdogYVNKwe/3a9zK9ddfj5KSEj1OU/HIy8uDw+GA2+1Wzz33nPzud7/DihUrkEwmUVtbi3PPPRdf+9rXUFZWhtLSUpXtRWXWSF5eHgoKCtSIESPkH//4B3bv3i21tbVqOE/rUO2gQP//cRsu5JEdT+SGMRm+3+/XeccmgzTLhvJzNoJZyNSoEPT29ko4HEYoFEI0GkVPTw+CwaAu29nR0aGt5Z6eHqTTabS1taG5uVkTKACUlZWhra0NNpsNpaWl6OzshOnCdbvdmDp1KmbOnAm/34+SkhJdsrO0tFSX7vT5fMjPz0dxcTHy8/OV0+m0xJwjkQi8Xq8eD0E1/WlXAgD/+Mc/8O1vf1vHjh0Oh3b33n///XLnnXcCAM477zzcfvvtmDhxogqFQtr6TyQSUErB4/Foa5QaOwCmmElRUZE67rjjsHbtWr1m0WhU36enpwebN2/Wn8ViMQvaeygAjSl0zJALx0+FjBZ0Op1GNBrVc/z73/9effLJJ/KNb3wD3d3dlspt2TFy3m/dunU61S6bqVCg5+fnq7q6Olm6dOkgoFEymcTGjRtx1FFHWUJBNptNo9Dtdjt27NghBQUFOm2vra0Nra2taGtrQ3d3N2KxGLZs2YJoNIpUKoWNGzeiu7tbKxd2ux3Nzc0WBkhFga+5Vw4//HAsXrwYGzduxKGHHnqwSlx/yyXkOpT3zgxpmFUOzWaeFyAig+ohmC2VSmHnzp2STCbR0tKCZDKJefPmKSoU8XgcZWVl6oMPPpDvfe97WL9+vabLl156CRdffLHMnj1bdXd3i8vlUsRnBINB+e53v4vXXnsNwIAhNGPGDIwdOxYAUF5ervmP3+9HUVERli9fjh//+MfYsGED5syZo/dXOByG3++H2+2GzWbDjh075O6778af/vQnxONxzJ07F7fccgtOOeUUlX1oU3d3t4gIioqKlM1mg9/v10aW2+3G4Ycfjg8++AC7d+/GIYccsk9w3/7aQYH+/3hLJpOa4QPQTDcbzGRaz8FgUOLxOCoqKhQAbNiwQbq7u7lpEA6HkUgk0NTUhI6ODkSjURBxGQwGEQwGNUNly1YszBhvVVUVRo8ejbq6OjgcDn0gxuOPPw673Y62tjaICCZMmIDXXnsNtbW1ytTizb/ZzzGbCcijWzoQCOiTuCjQbTYbXC4XxowZoyorK2Xv3r36xC6fz4fe3l4sWLBAfvGLX+DDDz9EdXU1vvOd7+CHP/yh8vv96OrqkkAgoAiIoWJES5abjClsPp8PPp9PJZNJjB49GgAsBVDy8vI0wKY/7i+jRo1SpgJmWjemhZ4df85WzKhsMCTCIjBAn8Lg9XpxwgknqDVr1sh5552HFStW6HUlmth8vohg/fr16OrqkvLycr1OHC9fO51OzJw5E0uXLrXQBF3va9aswVFHHTVIQaEXyW63Y+3atTjzzDMtn2fnoXMOTYFCxaffFYsdO3agtrZWhx84f5z3VCqFI488Eg8++CCWLVuGI444YtDBOP9bWy4GwX/SzDDJ/hQg0lE/zgWhUEii0Shqa2vVjh07ZP369Whra0NnZyeam5vR1taG3t5eNDY2oqenB6lUCi0tLXA4HIjFYpg8eTLeeecdKS4uVtwD9913n9x3330IBoM6BJCXl4eOjg48++yzmD17Nrxer6J7fenSpXL++eejvb1dgyjdbjduvPFG/OAHP1DMcjHHRSXE7/dLMpnEypUrLQh4r9cLm82Gnp4erFq1Sm644QYsWbIEpaWleOSRR3DBBRcor9erwXQ840ApBR56RF7A0EI/+A+HHXYYEokEPvvsM8ybN+/fWq+DAv3/8UYARiwWQyQSEQrbnp4exONx7Ny5E5lMBlu2bEFXVxdEBBs3bkReXh7OPvts2bp1K1566SW0t7drJCUAnTtsxj+dTidKSkpQVlaG2tpauN1ulJeXo7CwEBUVFcx91lY0UePl5eWKAoBx5lgshg0bNsg///lPzXTa2tpQWlqqY+3U8LNBXwD0ASWMMTE2Z6JgudFo6dKC5kEiLpcLc+fOxaJFi7Bp0yapq6tTmUwGd9xxhzz99NNobW1FQUEBzjnnHFx99dWgMM/Pz1d0A5qgN1qYVLJ4yEowGBSn06m8Xi9mzZplWT+Xy6VTw5xOJzo6OvDFF19g5MiRlgwF/qXQNlMLTRc7hTdThRjTpzLHFMJ+VyAikQhcLhdGjRqlXnnlFbn22mvx2muvaaaSzaRTqRTa2trQ09ODyspKnZlgrgcVzNraWstvzZh5Y2MjAOj4PcfA2DeZopmaZFp+ZnO73VoJoYuUfQKAPXv2aGXIpBfOWSaTwSGHHAKgD+nOefx3XJr/r7Xsucpu/6kXgiG1jo4OBINBhMNhHSoLBoMa79La2orOzk5dW4En5l1zzTWyePFirFixQitcZroYMR21tbWora1FYWEhVq1ahS1btqC9vR1VVVVYs2aN3HrrrXjjjTe0p4yKIUM5zz33HC6//HKpr69XnZ2dcs899+Chhx6C2+224DRisRja2tqglEJ+fr7G/3R0dEhJSYmiK72qqgr5+floamqyuNv7vZDyi1/8Aj/96U8BANOmTcPDDz+Mk046STHUFggEdFos946pXLlcLjD0Q2/R5MmTAfSljpohqgNpwwp0En028Q+lsZHJAIMBXENpikOhnM34iukS3Fcz3RLZedjZz+Imz2UTs2/mOM1UDACD8q+BAUQmMGBdAwMbi4IrlUphx44dkkql0NPTg9bWVoI20NLSonOOW1paEIvF9EaJRCIayMTxm8AwxnWi0SjWrVuHUCik57i+vh6zZs2CUgoTJ05EQUEBRowYAZfLBZ/Pp13cHo9Hma7g/TUy4IqKCkWmSgFbX1+PRYsW6e+Gw2GsXbsWxx13nHbBhcNhnW8tMnB4CC3c7LUywXlkDoFAQMeFzdrleXl5mDBhAl599VWEw2E0NTXJZZddhuXLl2vFZt68ebjjjjvAdJiioiLV2dkpxcXFiuPnM+muJ+KWFrDX61W0Juvr6/Xa07Lg/6TTjRs34owzztAFZTweDyKRCEREnzPO+DpzWYdyVZp7koVdPB6PBkGaAKVwOIzKykr1hz/8AZlMRl599VVLahfnDOgToB9++CHq6ur0OtEFz+/GYjHMnDlz0NpwzExpo8JBy43/x+NxnHjiiUin0xZF0+QT7Hu2MOd9qSx89NFHuPDCC/U88qAZ7tVkMony8nKMGjVKA/Z6e3stiOf/bzWuUU9Pj07xBPq8aCZYFBgAsXLvUWk1D+thiEkppXmNKSB5H+4tv98Ppmz2H7Aj5eXlyu12Y9u2bfLZZ5/BZrNh3bp1OmTW0NCgBXRvby8SiQSi0Sii0agl3sxGjyEFX3V1NYqKihAKhfDII49oFDgA3H///Rg/fjwKCwvh9XpRWloKEUEgEEBpaalSSuGBBx6Qu+66Cxs2bMDKlSvl1ltvxc6dO/V9YrGYpgVmXogInnrqKVxwwQVy2WWXYfXq1ZqnAFa+sWTJEgt9AEBZWZmKRqPw+/1IpVIoKytTU6ZMkWXLlul9mJeXh9WrV8uPfvQjTUdjx47F888/j+nTpyvKItMo4RpRqJtVF+m2pyepurpajRgxQlavXo1QKGQBBubacrbQs9MIgIGcXNNtuj/0I92JdJmZwIih8hhzaVxYIp6BAWXDdJWYjMJ0a2anDfG7QwG1suNudJkGg0Gx2WzK6XRahHk2E+7q6pLCwkK1dOlSufnmm/Hxxx8jnU7D6/UiHA7rZ2ePnSjfgoICjBs3DsXFxSgoKIDH48HkyZPh8XhQUVGBoqIieDwetLW14fvf/z5aW1v170tKSnDRRRfh29/+tnb7cE32J7iHOpgju28UDKaAcTqdGDt2rL43rd1t27bh+OOP15qx3+/Xm5TjN1NChlO+uGHMOF2/5i6JREIde+yxuOeee3DDDTdgz549GvAF9HkprrzyShQWFirT0mGd8+wCGGY8LxKJSElJiU77JD07nU41fvx42bZtm8Y4sPG7Zh9YuIVxfQCanoGBPPt+RU763YzKLDpD9DrXsKenRwPK3G43otGoRQl97LHHEAwG8e6771oQwCySk0wmLcA4rou5Nm63G1VVVRpsyTmw2WxIJBJoaWlBR0eHFBQUKLrYTWXd4XAgEAioMWPG6FQ03oPWDOefLTuNjnPa0NCgBbkZwrDZbDo1TURUWVmZ7N69W6feDbVH/52WfUStWWCEih7DIM3NzVJUVKQKCgoU9zzduNl4CZ5lT1pg7QQiz91uN7Zv3y48S761tRWxWAwtLS06jLZ582b4fD7s2bMHn3/+OU444QQ89NBD8tFHH+Gqq67Sz+PJeOTp/eA0zVcrKipQUVGBqqoqVFVVoaysDD6fD2VlZfB6vaioqIDb7YbP50NlZaX6/PPP5ZxzzkFXV5dFCauoqMBpp52mhkpRIwCutrYWPT09uPfee9HV1YWdO3fC6XRqbxcwoOxlMhmcfPLJSCQS+PWvf42XX37ZkvVC2jR5an+oUfLz8zX4LjvXPC8vDzU1NVi/fj22bt0q48ePV7///e/lRz/6kZ4nu92Oxx9/XAtz4nfC4TDy8/N1Cltra6sUFhYqt9uNrq4uMd3vZrqqiGDcuHHYs2cPXC6X2p+Buq827LdNhCI3JRd6KGAJhQSJ2szlG6pzXFAyi30J+H01VvNiM/MN6dY1XbZkLmZ/96eIDFWth9oomQ81bVMLDIVCOl8yFArB4/GgqKhIPfjgg/LjH//Ywui//vWvI5FIYPTo0RgxYgTy8/O1a7uwsBBOp1MXUfB6vSobeUpN3NwcDz30kLS2tmphSlcvhUwsFhOfz6fMAgmmy5PXcAyPbnMzbYnzMH36dItSlE6nsWzZMlx22WUWJYK/NcdlMu/9NbpvTSvA6XQiEAgofu5wOPD5558jFotBKYXy8nJ0dHRg/vz5mD9/vgIGAGocA132sVhMp/hFIhGkUikpKChQbrdbUVBm0+vhhx+ua7IDA4ovheOyZcs0nXu9XgvdUDEhTdMi6/cCKfN72Tn+BPJReLB/tAQikQizBdQdd9wh27dvx9atWy1IdjLADRs2WOaC60VryOl0auwEC8kwNRIAdu/ejd27d6OwsFArAWb+sc1mg9frxRFHHIGXX35ZV4/Ly8vT3grSF5vp/SPNiAg2bNiA5uZmqaurU6ZXhJ+TJmbOnIk//vGP2Llzp4wfP17nyf8nzfQQmtXSAGjrmhkFdrsdVVVVqn//abpKp9MIBoNC0CnLAkciERxxxBGYOHGi6vdwiNvtVh6PBzt27JDf/va3eOaZZ9Dd3a3TNJkiyGZWTvN6vXj33Xdx/vnn6yNw+xHWuPvuuyEiKC8v14K3uLgYI0aMUNkYF3NeqXx6PB6Lx+nII49Ufr9fCGjkZ5s3b9Z0xpQyM1zkdrsxZ84ceL1erFq1So9DKaUBndlGzyGHHIKvf/3r+OCDD7B3717dVxoNwIA3D+gT6Lt27UJ9fb1FPpiFixwOB6ZPn44XX3wRr7/+OtauXSsvvvgigL66El1dXfjJT36CU045RQFAPB4Xv9+vTEOQXpTy8nLFuSLPTSaTWrGk4PZ4PKivr8cnn3yC5cuXy1FHHfU/b6Gn02nLxJjl7kzXeHb8byjhbcZKiY7cV86iaS3vr5kVx5igD0AfEUmkNtHZzHPu7OxEOBxGZ2endmnTSiSy2ufz4ZBDDkFpaSkmTpyIyZMno6ysTC8aFYG2tjbxer3K5/Np4VpSUqK6u7vF7/er/Px87Nq1Sy699FIsWrRI17jOz8/HmDFj8OCDD6K4uFhxkRk3BzBkHIVMMZ1Ow+PxWEBa1BJLS0sBQMdRvV4vtm3bptfM5XIpk4GzcVymRbW/RqZFwU4lBwDq6uqQn5+vBRsAMBeY605lhxuOlhk31XDNLCiSSCTE6XSqvLw8rSBOnjwZI0aMwK5duwD00dXevXsB9KHagT6hSaWTzJcC3e/3Y8+ePdJ/BKjKZDJqy5YtsmvXLnz88cdoampinFErDP3pUbqPnBPS/oYNGxAMBsXv92sBxPmgWxoAtm/fLi0tLWhtbUV3dzfy8/MxduxYjB49Gsx/N922Ju1TWSDql/3oVwJw5JFHqp/+9KdywQUXDCpMlE6nsXXrVu2qJXMiLTBW7fP5VH19vWzbtk0LLjM8sWfPHkyfPl3XqDfL83JtJ02apMcNDChyrADGPUllwBQoVJQ4R3V1dRa0PvcHXddz587F008/jRUrVmD8+PH/Vowyu2WfYpddryGTyWil7bXXXpM1a9agq6sL3d3daGhoQDAY1CmOwWBQx4a5T48//ni88sorcLlceq0XLlwod911F1avXq2F3AknnIAZM2agvLwcBQUFKC0thdvtRklJCaqqqlBaWqpeffVVueiii9DS0oJEIqGVgHQ6ja985Svw+/2KQEkKRfbHDH+aPJ9gU47fZrNpujnssMOwYMECHTIiyIyKHw82oneISqzf71eVlZWyfft2+P1+RKNRzac4x2YYpqCgAHPmzFGnnXaavPPOO/re5smElE+k023btmHGjBkWXks+xvDY9OnTUVhYiJ/85CeaF4TDYXR1dWHs2LH4xje+oXmr2+1W3MNMy+yvsSFer1ex/kZpaemg9Fn2NZVKYfLkyXC5XFi7di2OPfbYA6bHYTmm3W63WMBmaU8SbTZQx7TSyeizwTvcmEyx4WcmmjsXlxi9BA6HA6FQCCtWrJDnnnsO77//vmYITDMy3YfZAJx9Iaz5HbvdjpEjR2LKlCkyd+5cnHzyyZg1a5aKxWIoKyvT+bZ+vx+MwRYWFqqenh688sorcv3116Ojo0O7tpTqy9les2YNtm7disMPP1wTnFnpKvs0KjZaAt3d3bo0IgkDAGbPno1//vOfujqY3W7HypUrEY1GtXvWjPdnK2Z8xnCgG1NTNnOiU6kUSkpK1Pjx42XVqlU6Hr59+3a0t7dLQUGB6u3t1WUZgT7BSo09O369r0Y67P+dolDhb8vLy1VBQYHs2rVLPysUCqG0tBQzZszQYzDd3RQeIn0V5CorKxUAfPTRR/Lwww9jwYIF+vl0U5uNc0pN3pwjh8OB3t5eNDQ0YPr06dqqooBjMZ6uri755z//iUsvvVS7P8PhsI6tH3vssXL00Ufj1FNPxfjx48H8XGr6qVQK4XAYhYWFui60x+PR4J9QKITzzz9fvf/++/LUU09pwB9bQ0MDmpqaZPLkycqsW01Gz+9OmzZNpwSZdJBOp7Fp0ybMnz8fwEAYLNtDRiAU0Fd2ljRUVFSkEczBYFD/zqQHKkrpdBp021M54TybnpspU6YAAFasWIGvf/3r/yPuduIB0um0RkyHw2GNtejt7cXOnTvl3nvvxZNPPmkZA8uFsn5CRUUFCgsLkZ+fr+O/L730Ei6//HJ58cUXFQBcc8018sYbb2jQIQ2oE044ATfeeKMiPbGCZF5eHkKhEHw+Hw4//HDk5eXpQkdUhIPBIHp6elBcXAwAuggQMGBQmB48/s/57l87fZIfvTjTpk3DggULtPJFQcr4sMnPTAXCbrdj4sSJaGxs1KFI9sVut2uZQWzHpEmTkE6nceWVV+Ltt9/W6WBmWMbMjgGAtWvX4qtf/SqAAT5nFs8BgAkTJqC7u1tjlHi6Yjwex8iRIzF9+nRFech5Ig/j+gYCAe0JKi0tVV1dXbJr1y5s374dq1evRmNjIwoLC3WNjra2NjidTixduhTf/e53/+fz0NPpNMLhsHg8HkWhTAuCCNjsTco21FnInGC+ZyoLJigEyK3wg81mQ0dHhxQVFalPP/1ULr/8cjQ2NmpmWldXR3e3PnpyxIgRuhBJUVERfD4fCgsLtYUTiUQQDAYRiUTQ0tKCLVu24PPPP8fKlSvxt7/9DX/729/wyCOPoLy8XC688EJcddVVWuvy+/3weDyKhQm++93vyosvvqg3AI+7NHOFGxoacNhhh2mXo4nSHs6KyMvLU5w7s/Ql71dYWIju7m5kMhm0tLRg69atMn36dMX1M1HBJnCQbTgrmYKA62lWLnO5XJgwYQKWL1+uvxsMBrFjxw7Mnj1bCx2/369jxKzHbB6Ssr/GeSbgyFQIuRkmTJiA3bt3Wyq2FRQUoKCgAMAAnXEcFAL9cTEBoH7+85/Lz372M4TDYa10EDDEuD1j2kTu8nlD4UG++OIL1NfXW4Bk5nfdbrdyuVzCuSCjo6L7/vvv45NPPsF9992HK664ApdffrlMmjRJMd7en3qjkfBUDM2ymQDwgx/8AH/961/R1tYGYMBFG4vF0NTUhMmTJ6O3t1fsdrvGC5hgvQkTJgCwnjlAofXpp5/ie9/7ngW1T8WdSv/8+fNx7733ory8HH6/HxUVFXC5XCgrK0NeXp46+eSTZcmSJXocJqbFnNcNGzZYcDr83KyzzTjvxo0bdZ//U5R7KpXSIUgqEHSlR6NRLF26VK655hoN0nI4HBg/fjz+8Ic/sGCKTnvMLo4DAMlkUv785z9jypQpsmTJEnz44YdaSadhkkgksGvXLr3/uffo9eA9i4uL6bXRWAcAOlWqoqJCC1/yeIaA2PaFlVJKqZ6eHgGgCNqbNWuWVo4Z1961axd27typ0d9sIqJDRF6vF0cffTT+/ve/AxgQwkMB8gCgsrISbrcbp512mpo/f7784x//2KchQNmzevVqrQxSMeT+Is+pqqpSVVVV0tHRAWAA9O31enH22WcDgAVFzzg/5zsvLw/Nzc1SWlqqgsGgLFiwAE899ZRG/NNjxv1CrxJxIf8ObeZqoVtirWzd3d3CXDuisLu7u9HZ2YmOjg6Ew2FdJICDzs/P1znLVVVVqK6uBuNCZilOIDc0er9rUV199dXy29/+Vr/f1dWF4447Du+//77KVjZMhYKNKGkAFkFibvpwOIzNmzfLv/71L7z55pv47LPPcNttt+H++++XH/7wh7SmlMfjwV//+le55JJL0NnZiYqKCu3mpYXOvrOQx1e+8hVNhBQODofDUn/b3Ey8qJG73W6L8KuqqkJvb6/eBPQg0DI0rTGOz7TM2Yaz0E1hAUDnZfN306dPx0svvaQZQyqVwoYNGzB79myk02kd7+0HBul4NhWa4Sz0xYsXy8aNG9HW1oZwOIxdu3ahpaUFoVBIW3ZbtmyxzKHP59NnfVMBCYfDFkZGpaSwsFC9/fbb8vjjjyMcDsPn8+lMAzbiEkjjZF5DARy5kT///HNcfPHF2htBtzQFl8fjwZw5cyyMOXst6I341a9+hT/+8Y+4/fbb5aqrrlL5+fm61kA0GpXCwkJFCwnoA2ZVVVWpYDAoU6ZMUd/61rfkoYce0velYFizZo22sJ1OpxYWprU+ZcoUzRD5l/1ctWoVotGo9jqYwEl69crLy9Ull1wySHEl8zzyyCOxdOnSQdkNDGFwjj///HNtZbEvJs/qx06o2tpaYXW5f+c0q+wWj8fh9/stXr/+9ZW3334bLFoE9O2Vnp4edHd3o6amxhIuoWCmYkRaeOGFF9T06dPl1ltvtTyXijrdyR988AHC4bDmC8z+4Fz29vaipKREzZs3TxYuXKj5AYXvli1bNBqb/eGpY/zfxDXwdXd3t5SUlKi8vDytLDJkVl5eboldc77a29u1F8tsxGYAQE1NjeYBdIOzUYFOJBIoLi7W9+rt7cX3v/99vPvuuwBgyf6hO50x/jVr1mhvJY0SEytCL/TcuXOxYMECjWMKhUJIJpOYOnUqOFZzT/AvQxpVVVXqiy++kFtuuQXvvPMOgIHwEZUDs96HiKC4uBgzZ85ET0+PXsOcW7Z1ln2xwyKCL774Qq677jqZP3++nHjiiTJ79mwZO3aslJeXi8fjEaWUABjystvtYrfbLf+7XC6ZOnWqXHTRRfLiiy9KY2Oj8FmMBw/Xv7/97W8yZcoUASA+n0/y8vIEgOTl5YnH45G9e/dKKBTSC5ZIJPRBGYwnxuNx/TnBG4y9J5NJFgGw1N3uPwtbbr/9dikuLhYAMnbsWLnjjjvk+uuvF4fDIR6PR/x+vx5zv8WlL4fDIQDkW9/6loj0pUER7ESN33R1kYmZF93C/H4wGISIoLu7W8aMGSNKKf0cu90ud911l6TTaXR0dIi5vua9Wb2Ic7S/i/1mX1kNiYz9b3/7m5SUlFjGe8stt0g/ElrWr18vq1atkoULF8pf/vIXeeKJJ+QnP/mJPPbYY9LS0iLZ482+jj/+eAkEAgJAbDabnlun06np0W63i8fjscx9aWmpcIwcAxkVY3Yc03e+8x29frzniBEj9JjcbremOdK4+Zp/2T+PxyPz588XMlrTsuxH0EOkr351VVWVpmmOyxynw+GQ/Px8ff+rr75aWltbhePgPTm2UCgE8zMRwapVq/Qamf0977zzREQQDoe10hIKhWD2u79mtthsNsv+z8vLk7KyMtmzZ4/EYjHNS0hTVKBJh8SxRKNRRCIRff8nnnhCvF6vHrv516Spuro6IU0yzMbxhcNhfb8zzzxTPB6P7Nq1S4bjLblekUhEz1Emk8Gbb74p8+fP12tdUFAgAPT8OBwO2bVrl0QiEZ0KRj5j9juTyWhvYXl5ubhcLk0LnBPOtVJK9uzZo8fEeeX9mPnwve99bxA/UkrJ17/+db3WJv9h6I/9IV9mf3lv0mwsFtO03NnZKQUFBeL1ekUppfv+2GOPCcfN33Dd2OcPP/xQ72vSdvYeJs/fvn27ZS3nzp0rNpvNsk/MPeRyucRut8vu3bu1vGHYhIoVx/GjH/3Iwrftdrs4HA5ZvHixmOvOPRWJRLRHIpVKobu7W7761a/q35MWTNolr1BKydixY+Wpp54S9uNAaVG7FvlGKBTSN2LZSxHBU089JSUlJZoofT6fAJDy8nKpq6uTo446Sk4//XQ5//zz5bvf/a785Cc/kfvuu0/+8Ic/yF/+8hd5++235e2335Y//elPct9998kVV1wh55xzjvj9fr1QBQUFcvHFF8vSpUstC0TiNIVXMBjE1VdfvU8Fgtf777+vGQYZp8mshxMYw12cp3vvvVcToLnZTCWG7zscDougHzNmjFCg8J4EdHDzcEPSHcoNYzJcKipMPTr11FM181NKic1mk9NPP13PR//xopoG+Exe4XBYP4uCjy5uEjCVpJ07d8qGDRtk8eLF8ve//11effVV+cMf/iDf/va3paioyDIXeXl5UlVVJU6n0zI/FIxKKfF4PLJ69WoxFRszB5+C6Z577hm0Qcxnme+ZDKE/rq6ZHhU40pZJM8cee+ygdTWZBS+uaV5enh6L+V3+dTqdUlFRIcFgUAN8+DyuP8d32mmnicPh0OMxx8C15XtUOM4991wxFRUyqv4StUilUvp9Pvub3/ymOBwOC+M79NBDxeyb+dekgxNOOMEizMkbAMiSJUt0Xzi/xLZwTxMQZAoxfv7pp5/qsft8Pgu9mJfdbpfNmzcLFUlTaeCeSSaTuPfeewWALFiwQEwhlZ0+ZF78jHnF2Z/zvY6ODrnrrruksLBQ07M5n+b15ptvDqusUtHOZDL44IMP9DqTjkxB43A45OOPP9aCgDyTbmXuoWeeecayL7hu9fX1QmEkMqCYkw65J7iONDBMHpJKpbRXkLzkqKOOsjwHgHzta18T0iNpkfucSl93d7eMGzdu0F7LVuamTJkiu3fvFvYrFovhiy++GJL/Zv/+nXfesfAXc99T7r3wwgt6LxcWFupxLFy4UM81aY30xv8zmQzef/99y/O9Xq+43W5LvyjQzz77bNm2bZulTwcs0M1/zE22d+9eERFs2LBBLrroIj0QMo0rr7xS+L3u7m6hRkJLd38PNQWViOCNN96Q73znOzJr1ixNaBdccIGsW7dOKKx2794tvO/f//53mTVr1pAaW/ZFbYeKABkan/2fCvRoNKoFa0tLi5iM0dxwZPYU+tSqAUhJSYls27ZNuCG4EU0PBRloNBpFe3u7kEmHw2F0dHRIJjNQ7J/z9IMf/MCyZgDk6KOPFjLWVCql54VMMJPJYP369fr+pheAG7W/RjL+9a9/yejRo6WmpkZKSkrE7/eL1+u1EKtp+dGSzFZu2Ddzsyml5K9//atQseDacaPRwnzhhReGFK7ZQjSbAQYCAVm+fLlw3Kbw4/O4tnPmzLH0jRs8W1Hh+06n06JImUqFOX4qLBQI5iYmTV1zzTWDlCGToZmM0vQePPDAA0ImQ8FgWsemh0pE8Oyzz1oUDq4d9x15A79vWmiXXXaZpR/m2F988UUtuMLhsKZNWn+kOZM/kDHyND6Tbsz9ZI7fZrPJu+++q5ks+RD7yTn+05/+JDabTX75y1/mJNCHYqyZTEZ77/icxYsXy1lnnTWIRkzvjDlHv/jFL3IS6BRuIoIXX3zRotxRmeP/v/nNb4RlRLlGnF/um0WLFonNZhtEk5WVlbJ161ahh46C3JyH/rMeNP+Mx+PaO0fBli3Qrr/+eosHzel0yuzZs4V0ZPI8rhs9onPnztWGiCmgOY9KKTn++OOFPIxjbWtrk7PPPnvQvjD5gVJKHn/8cS0bqEiSD3DPfPrpp5pvmP148803cxLov/rVr8TpdFoMOJM3FhYWitvtll/84heaF7S1tVn4woFcNi4OcwAZqygvL1cvvviinHXWWXjuuecgIhq1yRhdJpNBWVmZKigoUIxF0N0QjUZZrhThcBh0L9G1QWR6vyWpnnjiCfXuu+/i8ccfx6GHHoo///nPOPLII/Hkk08KAJ0Ped9998lpp52GZcuWWWIP+2pffPEFgD7FBRg415mxp/+0uVwuFBUVqa6uLqmsrFTPP/+8euihhzRoivF7xqx5rB8BhQAQDAaxefNmS2yFcTRqsARDeTwelJSUKAo3n88HIkYZC6e1zzgP45dAX1GTX//61/Lggw/KbbfdJjfccINccsklcsQRR8iYMWNkwoQJwnknsQKwxE1ZU0BEsHPnTjQ0NGjMBNeXjYASxrzYCAo0MQomhkBEsG7dOgvS3ozDMtY2ZcoUS/66WXCCf03AHu+fTCaxevVqPd8cm5k2ZrPZEAgENHLX4/FoIAuLgfCZvCcbX2djNdji8bjO9WbfeD+zz4cddhiAAfrlGEwgDwBdbUophZaWFjzyyCP4/PPPxczJZeyRvxcZSEWaP3++Pv6VwJ6Ojg40NDRoesxGOLPNmjVLP5tj55ysWLFCx1HNegWkdYLlent70d3dLcFgUBiTJL6AtRjMmDeFqYmzIfjS3D/meAHotKBVq1YNiw8BBuieAgMYOM2LIKYHHnhALrjgArzxxhsABvAX7At/Y9LC559/PmxKKH9fUFCgEokEzjvvPHXPPfdoHkYALce7adMmuFwujbvJBrQBfQDR/Px8/T770N7ejp07d+r4MYF+0me5CotoEWBIJD1TtGit95eJlebmZmlsbJTu7m4LmI1I97179+qOcW3Mv263G5MnTx6wPI35YJ9FBFVVVZY4tEjfaYMmQpxprIAVn0V6IQ8gjoPAzlQqhQkTJiAQCOiaJmw7duzIiX5YqZMnL3K/FhcXo6OjA9OmTcPatWtx7bXXqs7OTnG5XBoN/+9gPBwmMrqnp0fnrt5yyy14/vnndUlGM98wGAxizZo1FmAVm4liFRGdb56dY043DQmkH9ylLrvsMlx88cV44okn5P7778e1116LlStXyqWXXorbb78d//jHPzRKPD8/X/dvX23lypWWnGZucoJy/tNms9kQjUZRVFSkwuEwwuGwfP/731cnnHCCnH766WhsbERpaSna29s18t4sxcgUoE2bNuGUU06xFCUgETKdrz/1TFwuF3bv3o14PI5t27YhleorHLNjxw5dKcpms+nCDCYRt7a24uc//7l2K2U3j8eDWCyGZcuW4aKLLtLWCwVGMpnUOaMzZsxAYWGhBp8RxMK1NYuusJAEAAuzJZDJVLAItFu3bh0Aa3ET/pZMZPTo0SgtLbWAB9mymTrX2+FwIB6P48MPP8TFF188qJoUjxgl7RIB7vf7dbnXH/zgBzj55JN1+VKTiWc/35x/kwkQbU0aMEGhzF6YNm2aZQ35lzUE+DsyCq/Xi0gkgt27d+N3v/sdZs6cqZkRlRHOowlkrKysVEcddZT85S9/0dY4AGzatAmzZs3SCtxQQD/zbHSui72/UuPKlSv19826BwQisWSt0+nU1bM4TipO8+fPx3PPPWfJpGF6oSmwqbxzTqkYmd+rqalBUVER1q5dq/u7v2Yqm5w/KrSbNm2SW2+9FR999BH27NkDYKCy31C80Vz7L774Qmem7K95vV7NL8LhMC677DKEw2H89Kc/1VX6mDq5Zs0ai1Jo1pKg0KqurlYjR44UU5kk/W3fvh3z5s1DJpPRAr3/cw12a2xslL///e9IJBKIRCJobW1FJBJBV1cXOjs79dXV1YVYLIbS0lKL8QL0AZZbWlpQU1MziJbMbAkzrdSkO9JyJpNBdXW1/o6pmM+dO1edeOKJsmjRIgs/pVxSSllkA3mcmSWRTqdRWFioamtrhbyUc51daW9fjd/n2RkEHobDYTzwwAP44Q9/qILBoPQD/HSBqEAgwGk/sEbXAN06//rXvzTILPvy+/1SWFgogUBA3G63tLS0aLcVXRTZrjPThcb4ChlROp0GATzZLtVwOIyNGzfKiSeeqN2zNpttkOt6uGvkyJGyd+9e7a4ns6Ib6j91ubMwA8dAi7q7u1v27Nkjs2fP1i44u91uAVCZbjiGMOjKIuKVno54PI53331Xu1rNMQ4VVzTdnqYreF/fczgcFtfScccdp9fFdDv2Ky16zLNnzxal1JAuYV6my8vlcg0KlWS7pdnfQw89VNOXGROl5UGg4nHHHSdKqUHuuaFc8cBAzHnChAlCNDjpk+Oi5dPb24tvfvObGswybdo0WbVqlYgImpqapLKy0rImdCtmj5t945rbbDb5xje+YQHl0PVP16aIoK2tTcdkzcsMH5hz7na79fPz8/O1W58uTroTSV8cZ3/pTAtN2e12ue6668SMm3PPUEFLpVJoaWnR4Yds4NrYsWOlu7tbh3Do1syOR5vhpVgspnOjw+EwHn/8cd0nE5tgYjCcTqfMmDFDrydpxQR78rlHHnmkjBgxQjo7OyWXGLoJIGT/o9Eo7rvvPgtvNPdXdviI/TQxSARl5RJD5/yRT5977rl67UkfHo9H71lzrdhvruPXvvY1C22yb9ddd51Q2Jg0wnkMh8N48803xev1it/v3ycImrRjfh4IBCz85ZlnnhFimUy8EOWDiOCDDz6wzKE5p2aYgTzCBNmJCN58880heQ33a2lpqbS1tYkZkmAYwbzPUEDC0aNHC42i/bncuadM/M2cOXPkvffeE5MuyWfNPfFvudypKRcUFKg77rhDjjvuOGzatEkXe7D1VaMC0Je2ZZ70tX37dk1sZtyLFwdnCntgwP1ks9lQVlamUqkUiESny8fn82HChAnqvffeU2eddRZsNhuKi4t1oQHW0x2u0XKlFprt3v1PG9MTOD8s1lJQUKDy8/PV448/jhkzZmgmE4/HtcsdGLAKmUfLtAlqlUxJY41j/ob5q0opnebkcDj0e7TEeH9aFwB0MQse4sE1YXM6nVi5ciUaGhoEgBY2QF8qiNPp1Nb2YYcdZslL5ZrQXcp7830iSPn+UKEPzs2WLVuwe/duIb2YdQ/IhG02G6ZPnw4RsVR9YuO4sv8CwLZt27Bs2TKhR8n0JPH+DocDxx13HILBIM477zx88sknqqKiAr29vRg5cqTiKV4mbZt9MJ9nCo1MJqNTZwBYyk6mUilNr4FAQE2cOFGvoXkvtuyQAQtghEIhvPvuu3pu+FyTvkw6mTZtmn4G3cmrVq1CPB4XjjE7xVFEUFBQoEzaNFPk2trasHPnzkFuVb5OJpOWcByt1kAgAL/fD5/Pp0+hMkN1wEDdCs5HS0sLdu/eLWb5YTYqDTabDfX19QgGg9i2bRtyaaZ1mEwmBYAudUo+SSXX7J9pFZr0p5RCJBKx1PTfVwsGg6KUQnt7uwDQ7vcHH3wQxx57LBKJBLq7u3WN935A2JC1QdiH2bNnWzw0/N769evBIijsM+kjne4rnFNVVaXR3OQ9pheW/Ju0ztolPT09ev86nU4sX77cUmck25UO9HlTWPGSdJU9lpEjR+pqdua9UqkUTj/9dHX88ccPOa/EQbB6pumSN2k8lUpZTlA0Uj+xfv16GXxnaysvLwcwUD3u5ptvxqJFi9RRRx2lT3Tk+Hg+AzBQIOlAmw0APvvsMzn22GPlgQce0KfNdHd3a1cd3doFBQWWQjBbt27Vi8hiG4x9sXMUSiySQgKgVsTFyM/P1zFKalu0Jl544QV1xRVXoL29HUBfDiZrlw/X4vG43jgkKEObOeAJy24sipJIJDQDouZpt9sxa9Ys9fzzz1tKtNKFZrqX+s8LFhHR8wMMFD3h+ebjxo3T8SoTxMG5Momb92DNZDZq26YrjHMODORWdnZ28jPh9xi74zxOnjxZK3PAQPEHrmN2frwpvE2hb77P1z09PdrtbgohMgyWmuUJZ6Yw4ph4LzPkwk2USqXw7LPPWuiC80PlwGaz4ctf/jIAoLGxEYFAAMXFxYpryWcD0ALZdPFlu83McTY2NmLPnj1i9p2MlC7rvLy8QeMz19ZkciZWgnPzxhtv6MplpmChwCNd2PsrIRYWFuqYNtCnaA6FdzDH5nK5dGjA3Fc2mw3hcBjbtm3Ta2EqPBwjT/pjdTSbre9Qld27d0tLS4u0tLSgqqoKgK6nr2tFmGsWCoUsyjvnxhQwAPS506aLfn8tq3iIPsjn2GOP1dXtuGezwazZiqS5hmat8n01ngJYXl6uSCuJRAIlJSXqd7/7na5+R3pk0RLOo0kPLHzEExfZL67rhg0bEA6HdeyWdRnIk0QEtbW1qqKiQvN909vK8ZtzbQpic04+++wzPR8mnsP8bllZmRo3bpxFyWZj/6urq7V3lx4Y4pcA4Morr8TIkSP17/gMeh7Wrl2rPxsKX2Wz2XTxJPaL7YUXXhh2/Q4//HAAQGdnJxYuXIjbbrtN2Ww2fQQs1424M47TxOMcSLPdfffd8p3vfAcfffQRotGoPs6Svn6bzaaLnASDQZ2Mn5+fj0WLFlncDXTTmG4UMnYKb9OdaLfbLVWWiKAEYCkX29PTg8cee0x9+9vfhs1m0+fyZlt2+2otLS0A+rTr7DjKf9rME5GCwaAAsDDjZDKJsWPHqqefflr31+fzIRQKae1eKaWPK8y2LIjABPriabTWgD4FK1uLM/ECLIYAQBd/MQEiZiUxbhoeRRqJRLQwzcvLUyRyKhEcS11d3ZB1302hasbUqfRxjsw1JE6AF9AHPjG1WDYyGQAYPXq0ZQ6GqjDIPgJ9gpxM6/XXX0d/hSuN3iVTAPrWt7KyUn3/+9/HkiVL8Oijj4rT6cTu3bsF6Ds+0bTIsp+XTaOmNyMajepjc01mZWIKlFKoqakZpHyaljbHTCZGjEAmk8HSpUsRi8UGWdj8ndvt1oWOioqKUFZWpp/hdrvR0tKigY7ZAC/T00bGa+4rzvGePXsGgT35O1YM+/DDD+WKK66QOXPmyKxZs+SII46QmTNnYtKkSTj//PM1joHzykI35pzH43FdwMkEFpreHAAYN24cRARbt24dRCfZjR5B/tbr9Wrv1NixY3Wteyqv+/IAmooQaWLHjh3DPp+VCFOpviM9ue4ejwcTJ05Ujz32GABo5Xv37t16Xcxy16TReDyOsWPHAhgQsETqNzQ06NPMKKSBgbMB6F2sra0dJHRMuuY4WVGQYDDKgnQ6jQ0bNiASiQz6HfEOmUwGPp8PI0eOHHT4jTmfI0aM0N4SKuE0OOLxOM444wxVU1Oj+aDJWzKZDHbu3KnHme0h5XNKSkpQUFAwqETzq6++Ouz61dTUqDlz5qCgoACTJk3SZZwB6JMQgT6Pj8fj0R4RnvdxwK2kpGRQ4QtbVooFLzNuV1JSIkceeaSYsU2614myTiaTOubDuI7IQN50f6F7nTpkpjKYKSGxWExblOPHj7ekqmT3cahr/vz5wvtGIhEdM8xkMjpeJNKX0vH888/Lz3/+c3nkkUfk97//vbz33nuyatUqYRoHgTxmuo2Zt2jmcrIIR3d3t6RSKdxyyy26T9l9d7lc8uSTTwrvxzlhHIUhie9///uWmG322jHWO1Txjew8Y7MPjE2axSauueYaofbLOWLRHa7p5s2bxe1269xjs9CKzUiDUlmpI2YsdH/XJZdcIibNmEUvOOe7du3SaUzZYxju/g6HQ+666y7hfc2xpVIpdHV1iUhfeqbP55OioiL5/PPPhdaAmWdqxrpzHd8vfvELHV8249zET4gMxBK5dlzbfeVkZ4+PtRj6lU6NDeAYzKIeRx99tOXebrdbnnvuOeFepNJN5Z19XLhw4ZDPdjqdunCSaUmZBThEBEuXLrWkNQ6V6jfUZdKwUkouv/xyEbEW0yEPokHRD6iVE044QUxrOptPDfUZLwqmuXPnDsKomPRtxs5N/pmfny+zZs0SM42WrzknJu7ATO+iUk1swuuvv67X6swzzxTOMdNTyYfMlMMxY8YMOZ9vv/22cF3MZ5qYhxtvvHHQ/jILi9mNQk7m+phxZKfTKStXrhTOJZ9pYlhEBL/4xS/2SRO1tbWSjcnKNizT6TSWLl06qA9cn5NOOkk4R1xzeofZr3Q6jSlTpgwqjAZA/vznP2teQF4hItrbKtJXHMnkZdm1P/h7pnJmY9EO5LJNmjTJ4lIBrK5KU3sykZvhcBhNTU3o6ekR0zqz2ax1mwsKCtTy5cvl97//vTzxxBPy0ksvycKFC2XLli3idrtRWFiolFKsF49kMolQKCS8B12ryWRSnE4nbrvtNq395eJyB/qOyyOBmAh8pZQufdjS0iKPPPIILrroItx444246aabcPnll+Okk07CUUcdhblz58qPf/xj2b59u/Sfr6xzws3zuHm6UzweB9OdCgoKVE9Pj1x44YWYPHnykH1PpVJoaGjQ/1NbZGoKtVy6cLgWtCQ5XwyP0Bo214aEboY9st3zfK6I6JQlE53Nz0j8dXV1qq6uTmvb1IzpNq6urtZaJ8Mw/E4ujVaM6YqjO5X9CgQC2uoCrKlCw7VUKoWnn34a3d3dwlKXXV1dwjkpLCxU0WgUPp9Pvfbaa/rYxD179ojNZkNtba126XV3d+uQU3Zcb19t69atg/rLsfJvaWmppZwux8m/w42vtbVVf59WJJkI36flSLwM751KpbBjxw6N4jfjwqaLtLS0VJepNK3UZDKJ5ubmQZ6d7DFWV1ejrKzMcljQgTSOqbGx0aJsAwPnHZjlOUeMGIG2tjbtnTEbv5cLRmf06NGDPEgUBIAVa2DyT2YiRKNRIS3TOuVYyNxNrxXTn3haXywWw8knn6zuu+8+xONxNDc3w+FwoP+kR83zuB+5n8ePH6/DMmbbsmWLJTxinttBITlu3DgAAyVYgb7YMsOrDAma4wGs8iOZTOrULwo18jKOGQDGjBkzyAPIPVZRUWF536Qv04M0ceJE9ZWvfAX5+fm6nC3PRmhpadElc7O9GVwzm82GQw45RM+dudYvvviiRvz7/X5FZYqhkng8jm9961vqy1/+Mp555hk8++yzQm8a+TABufSeEte2r9r1+2033HCDxarJvvZn5djtdlmzZo22sE33O9/r6OgQPsO8vF6vHHHEEfLwww9LZ2enpNNprUE2NTVJd3e3UIPhZ9Rc5s2bl5P2zv57PB7ZuHGjmJr3UAjCSy65RP/OLNGXPT/f/OY3ZevWrVoDa2pqEgp4atsmYtnU9qmtDWVdmVXcaBFR0+Yc/P3vfx9klTscDhkzZowcccQR+1wzU1N2Op1agzatd/aLlmBtba2waI3JkOmRYAGIr3zlK2K32+UnP/mJLFiwQBYuXChr1qyRTZs2yY4dO+TCCy+0WF+8/1DFFrKv8vJyaWtr01YsNw8ZHhW1iy++eEj6zJVOHn74YRERPSbSsPkMEcF1110nAORHP/qR9Pb2IhQKob6+flDVNs7zcM896qijdDEQ0oqJLBbpKzIzdepUS0ENlYXo3x/9P/roo9pCNeeRXjBzn5nzyPufeeaZIiLam2Wignm/PXv2yMSJE4csWlJTUyM7d+4Uc9+ZCHTS+fz58y1WXC4X+8i1rq6ultbWVqFVawoMrq+I4Bvf+IaUlJTIunXrtJV4IJYRv8/Kc3a73VJuNPvvvtZmOP7JZxGRz7kzq6tJv7V+7bXXCgDZuXOnLrZCj4ppfYoIrr/+el2hzOz3N7/5TaEgItbGLG8qInjvvfd0Aanx48fL1KlT5ZhjjpEzzzxTLr74Yhk5cqRlfbLXiHR85513ar5v8mYzq2b16tXi8/kGeWLsdruce+65utxvdnaAWVQnk8ngs88+G5Lf+P1+Wb9+vUbcm1kn9MaKCH7+859r3sX9Z7fbxel0ysKFCyX7d5x7jmv9+vVSXFwsRUVFsnLlSjE/o2XOteba7y/zYl8XXn75ZQuTNZlg9iSak8n/X331VTER7pxcoplFBK+88ore6NmKQ3l5uYwcOVKWL1+uS/iRaPnaLDUq0ldZbrjNkt3v9957T0wmwvuHQiG0t7eLSF95W/ZzKPeK1+vVDGfMmDG62lRXV5eYbjOTaZqumGAwiFAoBJY0zL7GjRsn2cTI6m+8n0hfxagPP/xQFi9eLFu2bJHW1lZpa2uTUCiEH/zgB+L1enX6FNP7PB7PkLWNzbS17M9cLpesXbtW990cl+mGv//++8Xn8+lSitn1wu+55x5LpcFcKvyZF11zXDuz0h2ZzSOPPDKITnOlD7vdLjU1NbJ9+3ZdRY9rx3KQ/SBByWQyYJ1upsycc845AlhLQ+bqci8tLZXW1lbt5jTdj+aaf+1rX7OMLzstaF+X2+2WRx55RK+LGSIzGSgZISvTmTQxbtw4MRVLc09SuKdSKZx66qmDeAhp7NNPP9W16ykMzXRBEcEPf/jDQTW4h7tM5ZbzsW7dOiGNksGyv/z7wAMPCAB54403NG2ZShTHNpxAf+eddyz9yEWJM9fuz3/+sz5TwBQopkDPnnO+T9ro6OgQKoSHHXaYvP7663q9Tfc951+kr8KiycvZn2nTpgkNE96fYdT29nYtuL744gtdy9zMZEomk7j66qsHpe3xLw0sALp+PNeJzzLHyTMphlLOf/SjH8lQwtwU6qQzkT5DkH0xQ49vv/22cH75XQpc8jCmDJtnKXB/nHPOOTq1M1sZN6sjvvXWWwJApk6daqm+aCrHZondf+ey1dbWavSj2bKRfqarwXTVrl27tu9GhvuN7gtO0tixY5HJZDSRAAPnWLe2tmL37t047LDD8Omnn6Kzs1N4dCbdsvwNj6P70pe+pOgaHK5xDNu3bwcw4Bpiak0gEEAgEFAAdCEDasl07dA1yfibUn1nal9zzTW44447pLCwUO3Zs0f4faUUotGoRnG73W4lIjwfF9dddx0AWE7S6Xf7Y+/evUKXDwFrDD1EIhEBgJNOOgnHHHOMOvLII9XIkSNVWVmZ8vl8KhAI4JBDDkE0GsURRxyBm266CaeccgqAgUIcXCO6rU3wh4mAtvcX+6HL23TTmsAmoM8tFolE9HfpOmPqVGVlJUREI8ljsdgBpWQQvES6M1Gv7ENdXZ2eR/7l2ufSGhoa8OCDD8Ln82k3WyqVQn5+PgAgHA5Lfn6+UkrhZz/7GSoqKnDTTTdhy5YtMm/ePADQigaBf7m07u5uNDU1WcIipruYtM+qf3xfa+TDNKawAQMIX9P1mUtrbm5Gc3OzmMBCPpsMzG63W9LLTHpKJpPYvn27xZ3M75lt5syZyGQyGpiX7Q4eqpn94fd37NhhCWuZ7lT+5XwS+Mm5Mec3lzZ+/PghK6+Zr8lv2My+kn+aYC0AWiAQoMo+tre3iwl6BAaAgi6XC08++aR+Ls9n5/1MYGBdXZ3eI7yUUti9ezfPNtcYI7vdzgwlZbfbEYlEMGnSJMUwIJW6RCIBh8OBiooKizzhvbPnaP369UilUnqt+D1+nkgkUFBQoOrq6oYMg4wdO3a/NGLrT7cjqPCmm26C1+vVWQvkU5s2bdL3NmkGGHC/E/yanfpss9nw1ltv0cjUGRb0cLhcLg2sPPHEE9W1116LrVu34rLLLhP2g/vRZrNpfvPvNtuIESNQVVVl2aC8+b6ImkId6Eu94CBNJCcnyO1245BDDlEVFRWWmA2JnHnTDocDX/3qV7F582Yd0zaZPgWQ3+9XSimcdNJJOcfQbba+KlIm4fI1N05vby+KioosJT2BvkUzGWJRURH6rXJ4vV78/Oc/x29/+1sZOXKkMlH65sbOJrqvfe1rKCgosJSutdlsiMfjWL9+vf69adF0dXWJy+VSAFBUVKTM+CcAjWSeOXMmgL5c09tvv11ddtllKC0t1QqVWdbUjPWZa21ufAPprjcIv0PhU1dXB6fTic8//1y/R6LOz8/HvHnzUFJSorVlADkLdIfDgRUrVljW2jwWkxuOSNZsTMBwjTRgs9nw0ksvYdmyZeJwOCzHa8bjcXi9XsXY//Tp09WDDz6Inp4e3HzzzfB4PCgsLLTEwrmJh2uZTEanL5EZkOGbY6ivr7fQVK7jS6fTqKioADCAsyD9mwKV+8KsvMhn9Pb2Ys2aNbqSVnb/eT/SHp9FJRYA1qxZY/kNn2fiLurr6+FwOFBWVobS0tKcBLpJv3y9bNkyAANIa1Og8tk1NTVwu91Ys2aNRdBkI7WHaxUVFaipqRnUH/P32fehdQ9A73dmfpjKDukgkUjInXfeKeFwWEpLSxVxMBRUBQUFSimFnp4ezJgxQ82bN0+ZmBZzz3IeRo4cifLyckt/RfoAd2Z+PveaKchERB/ry7oU5jng9fX18Hg8lroOpuLF2HBDQ4POFgGsNT3MuZw+fbp+bdIfsStDrVM2H7Db7fjSl76kTj75ZG0okgZXr16t14A8Mds4LS0t1ZXtstPwEokEfvWrX2nsjVJKI9ZTqRQCgQB2794tbrcbd999t5o5cyaeffZZ/Pa3vxXWEDErXZoltw+02crKytQhhxwySHs2F8IkwOy2evVqRCIRMYmGjVaeiKCurs6S+kJLlwUKUqkU/H4/vvrVryKTyeja7UCfNU9ByTSR4/dRMGBf7ZNPPtEAOzIzarbM0ayqqlLTpk3Ti0bhxGIx/ahn+Hw+lJSU6GIYV155JTZu3KgRq4lE35m7xlnkotRALfOioiJ13nnnDcqVJXGZ1i8VjEAgoOjVoLCMRCKmNayAPms5EAhgw4YNsNvtOOaYY5SZijSUBUtvxVDWH+sdm2lYJmARAOrq6tQhhxyCjRs3wuFw6D5TaBcVFaG4uJj9hNPpHLJU677asmXL9GansmGmwSWTSVRXV6vq6uqcLePsRiXtvvvu02lcADS4xwSypFIpXHDBBerGG2/EggUL8Oabb+r6DP0MGABy6otSCkuXLtW/5Xv0DLGU6OTJk+Hz+QAMnZa3r5afn68t52yPG4WxUgPlhpkelg0S+uKLL/SeIH3yc87JjBkzBllS/A4FuinMSVO05sePH6/Kysrg8Xi0EjJcyzZA7HY7Fi9erN9jP/k553jEiBEYM2YM1q5dq1OHzPvlqjDl5+erurq6QbnS/J/zaKZcmW3t2rWIxWKS/Vz2VSkFt9ut3n77bTzyyCOMa4tpQAF9XhCfz4fu7m7Jz8+30GEymbQUi+kXULpgkWkVp1IpnZtt7nmbrS93mkqaiCAajYpZZIY8etKkSRaa4jPYKAMikYjlWdmeI9ISC0exmWu4P6WL4zIBqtdccw38fr8eq81mw8qVKy17nuBPkw8GAgFFr042D83Ly8PixYvxzDPPaD5Funa73ejq6pKRI0cqloC95557UF1djXvuuQcff/yxALB4gnnuwr/TbPb+ghLZLp+hFoJIZZNYd+7cqY+1NEEuZBZ2ux2BQAAzZszQk0N3eSqVgtfr1YI2Go2iubkZzz77rADQ51JT4LLmrc/nw6hRo3IedCaTwfr167UwYqyGGiTjbC6XC3V1dchkBg7/MK00jjkSiaCjowNer1cz2fPPPx+BQABut1u7xjleh8Oh0uk08vPzEYvFYLfbceqpp+r78bs2m00jy9koaPPy8izxGJ/PZ1kLWk8FBQWqoqICW7Zs0bWD3W43/H6/ZdMNtUGAAeQqv7d582aL8CU9mN4Wv9+PQCCAVatWaS3VtDILCgoUXVac6/6+Drt2qVRK16snM2L/yHAymT6kO71A2X3dX2OYhBbea6+9hn/961/i9/sRDAbF4/HoTAOXy4Xu7m6h8P7v//5v1NfX44033tCCkAyZBZqGayKCLVu2WNyNpiCim6+qqkordKTNXMZXXV2Nuro6BcCi9WdbT9kWunlvp9OpwylmiIbfo6tyzJgxit4sU0AAwM6dOy3hBCrS/N/pdMLr9aKoqIg0c0ChAfbHbrdj48aNmlmbXiEKfKUUCgoKVFVVFXbu3IloNKoFKse3LwMmuymlMGLECEvWhekFMffWUPxz+/btCIVCOsWKChPH0tvbC5/Ph3PPPRc//elP8de//lWKi4uVebiJadUXFhaqnp4euFwuTdfZ3jzyN3oWzPzqdDqNhoYGiyXKkIrNZkNbW5uwvkZRUZHyer2aZ/D+48ePV0VFRRaPA+eKjffevn279pBlhztI5+PGjdMeDAC6MmZpaalFGTEFMN+nl7inpwdKKRx77LHq6KOPtiicDQ0NmmebiqoZXrbb7Xq+TA805wwAHnnkEQSDQaGHkp/T2MpkMgiFQjj++OPVT3/6U+zatQt33HEHuru7xawPQpljKpq5NpuIYN68eRpEkJ2qYWqqjJWY2mQqlbLEIMhsqUHSSmVFI6fTqTtqt9v1a8Z4gL5iH/wuiUekLxWAm3zEiBE5adGm52H58uXCReIBIJlMRh/2AkCX+cuO3w81H9RO7XY7Vq1ahZdeekmSySSKi4tVJBLRFqqpONDbcPjhh2tNERiw0D/77DOdysBDN4g29fv9ljg4CS0SiWirOR6PS11dHTo7O7F582ZJJpOYO3eu5QhSWlRmCU0201IH+iqFdXZ2Ct2iZMLxeFyvTTQaxbx58xCLxbT7ysRRiAgOO+wwfV+e2jTcwTps27ZtQygUEhY9MoUIx5RMJnHKKado5SP7dKR9NdI9BREAXHfddWhtbZWCggJFsBFTXRhH7+npQVVVlXrrrbcG0QUP0silZTIZbNmyBYlEgtaYjsWbaVZutxtTp0617B82ej/4bLNdcMEFFpQyMFB1jyEdPmfv3r1iWugUPNFoFEuXLtVV3ZRSiEajYlrxyWTfoT2zZ8/W+9qk/zVr1qC5uVlYsIrPt9vtOvUV6Ku+1t7erosuDdW4B0xexXH3A6mwefNmIQ7CLJJF/pFKpXDcccchFAph1apVsNlsegzA4H2f3chX0uk0TjzxRA1iNYUoDQc2KhfZ/HPr1q1aAFDpdbvdOi7e09OD0aNHw26345xzzsE//vEPKSwsVCyTy3WkFykQCECpgbKt9v4iTi6XC9FoVKeWHXvssXrOzD3/2WefcY21wsu+lpaWKgCWve9yuRCPxy2W79FHH20RjpwPcy4A6NP4aNyxn0TpA30WOg0W8tGSkhKUlZVpRZWYJ9J2v3IqHEcgENB78s4777SkxnV3d2PXrl26cht5ARVq0sMJJ5xgoQ1TkQD6CpjdfffdiEajQgMVgK6A6HK5dCGl8847T11zzTV4//338Zvf/EZb9ua++XdOW7MppVBWVmY5Oo6DyaXRwiBhmcAwU0Otrq7WG4ACxNTYzHjb2rVrsWXLFjHBEiYzAvqs/FwsdDNmbpaF5OYzYywAMGrUqJxid2zcHCKC5557ToMhuHCmi9J0RxYXF6tDDz3UsuEzmQz27t2LYDComSWFqN/vRyKRQDAY1OENzl863VeUoKurS4qKitTXvvY1RCIRrFixAk6nE4cccsggkBKAnC2gxsZGPZcALEoFCZW5qbTkqNRRwzWrnRGFmkvjb7Zt26bdz9lxSpaXHTlypKVCXa7N7XZrBc7hcGDnzp245557AEBb5pFIRD8nHA4jEAjoYyWfeeYZbeXYbDbNOHJ1//eXLBXSuZmPygqKNltfznt2DA/oqxKmlEIgEEAoFNLu/4KCAhx99NEIBAIWXARRtU6nUx+ZbLPZ0N3drasqmvgKoO9MhNbWVs2RnU7nIPeA3W7XAFjASl8ifUftmv0GwLCCxoRMmjQJO3futLj7qRDT6qILm/uMc8A+hEIhNDU1WcYwFL6HtQu2bds2SPjmunZ2ux1lZWWWkMuBtq1bt2rPIYU656mzs1MCgQDGjBkDpRRKS0txxhlnYOvWrcITMm02mz4JkAoAXd4UdNmxaZvNhtGjR1t4CL/Tnx8Pt9ut9y+NIjO9Timl3e8iwkp9kkgkMGHChJz2+I4dOyzHTJvzyv7k5+erCRMmaBc20Idd8Hq9OgxGBZ5KGQF19OwRHAcAtbW1uPDCCy3vbdy4UStRLEDE8VO5LikpsfDRTCajS3gTz/Dkk0/qUuM8+ZAygPKQodIf/ehHOPHEE3H77bfjhRdekEwmo+motbVVcqVBs9mAgSMFgcGuslzap59+qjXLbCQsF2XixImasbCZsWJgQKi2tLTozW+iqklcACzW7XCNvzfPvyVRm4wD6GModG3m4tIkwQDA3/72N2zatEmouJhuKMAKRMvLy8PcuXMtrtZMJoPdu3ejsbFRKwoUYpwLj8ejiI6k8pCfn0/NUrHwQzqd1rXvJ06cqAmT854rsYgIVq5cqfvPZlrIdrsd06ZNQ15enp7jbPfZtGnTNOo/Wyjvr/GZS5cutXhr2Ex33fjx47VSeCD0ayp1xCf8+te/xgcffCB+vx/hcFij39PpNPx+PwWn8vl86uKLL1YXXnihBbF7IH3o6enB6tWrtYuVTCaTyWjXqc1mw5w5c7RVS5qz2WxaIe/p6UFRURGi0ShKS0vx1a9+FYcffrjiPJH5mv1SSmkA4ObNm/WRvtkMdu/evTpThOBK0q7pkp05c6Z+P9vVa4L/2EREo457e3sxe/ZszTcqKyv1nJuKvRmayMZikK7NzAg+08TGKKU0TX7xxRcWNzlwYDiF0aNHW7wkB9o+++wzDTKjQCe/Ky4uVj09PTj88MOVy+VCe3s7otEovv3tb2PTpk0SCASwa9cuAWApgdrb26v/p0A31wsADjnkEN1vkxfv2LEDjY2NQmvXdGNnG1c0kDweD9xuN/Lz85Xf79cFsIZr69evR3t7u5jzTQS6zWbTZWeZAcE2atSoQVkJJjbks88+0y50AvZo5RcXF6srrrhCA9eAAYyHGT8HrLJi5MiRupgNMHB+ADBAd5FIBHfffbeOyTNkRy+KUkp7cUaNGqV+9rOfIZlM4pZbbsHmzZsF6DMiysvL/6265DYAGDlypBo1apSeGP7NRaABsDDxbGFOxj969GhVWVmpf0OLHhhg2kR9U5hRszErCFETpKs01yYiWLFiBQDrIRYmViCTyWDcuHGKJ+TkOn6edAYAH3zwAfpdskK3IJkbmSn7PWnSJEv/uDmyD5gIh8MCDLiqAVgyBEg8Pp8PLpcLXMslS5YgHo9j4sSJIFCGjPpABDrnjYRpavP8O3bsWBQUFGDVqlUWhYGf19bW6pDBv9MIHAOsri66qkUEtbW1+nQmztFwje5IAFr7p1C+5557tGUOAKFQSEg3qVRKGBJKJBK49dZbUV9fbzmcJpfGdWY2ARVBujg5xkwmo/Edpjs4k8mgra1NZxR0dXVp4frAAw9YkOnmQRtk7KZb8R//+Mc++55IJLBlyxaO3VJp0MyeIHDIHB/njIqhCbYy3ZculwvTp09XeXl56Ozs1NZ3dqNQIf9wuVzw+/2WapcrV660GACkW9OirKmpQUVFhZ57WmjsY65x9MrKSs0/D8QzxLZixQrLPJljI0q6q6tLDj30UAB9INOlS5filltuQXd3t1RXVyvSXSaTocfJguNhGMBErY8YMUKNGTMGwECKF8MSVN5o5ZM/8RAuE6DK+aRgB/oO0MrFZdzW1obm5mbdd9I+5QZ55fTp0wEMeBcZz+b+Mz1skUgEr7/+OlasWCFUZkjTLBU7Z84cdfbZZ+v52bRpk6ZHHiJm1kIBgLKyMmV6oNioOAF9XrE333wTTz31lJgKh0mbHo8HoVBIotEo5syZox599FHs3r0bDz30kFaogX8zhp5MJuH3+/WJMtkW5XBNKaWL+g/lIqcLw+l0gm4Tc4AmqIEtOz5mPosuj66urpwHyb5s27YNZhyWbmH2l/FlHomZK1OmS9TlcmHJkiW6r6bWaLreOd6qqirL2Pma1oUxh4r/ZzIZrbXu3buX3gDZu3evbN68WVasWCHLly/HuHHjsHXrViQSCVRXV6vx48db8qPNdR5u7jZu3GixupjLb24Un8+nRo0ahcbGRkSjUb3h+byioiI1atQoi9cml8bNY4IaTUALN6xSCpWVlfoYz1ybSacULqS/9957D/fff784nU66M5XD4UBra6sUFxcrghR7e3sxevRoddddd2mwogmszKURh5Id8jKt0bq6Oq2wsIxuIBDQc0DFzul04o033kBxcbEuP0kBTOvH9EZ4vV50dnbKu+++C2Ag1svGcdCVaNIO+2cA4+D1ei2ChK83btxo8eKZ9GF6JUaMGIFPP/100PGmpDdeSilMmjQJiURCV7HjXly+fLkO/3FMZsioX1CqmpoabN261YJyNud+uMb5ZKbQgeb4k392dXXpEKPJK1ia1u12qxkzZiAvL0/zvldeeQVXX321pg/yYKDvlLZQKGThb7TWidFxOBy632Z/AOiyrNzHHJdplYfDYeno6JC9e/fKF198IW+99Za8+eab8rvf/U7eeuutnA0iKoomAI3/czxE5FOgjx8/3uK1Nb2tTqcTS5YswXPPPaf5MAW51+vVhtL3vvc97eldt24dgsGg9hRw/s37ejweTJo0ySLQiR9gv4LBIPx+P2699VasW7dOFzPjd6l4eb1eRaDz1Vdfra688ko8+eSTePzxx6UfWG052TTXZmPnzGMg2XJZEKX02b7COIKpuQEDG2TWrFmD4n8kNA6Y75n1xtnIaF0uF3bt2pUTwzQXJBgMYuvWrRY0IxmRqSETGJeL0GOsnBWvGhsbEYlELAA+M5ZMxL6IoLy8XL8254m5sbQSyPTIrDweD1paWuS6665DXV2dHHHEETjqqKMwa9YszJkzB5deeim2b9+OtWvX6upmdXV1Oi8y17GxTzt37tTHfJoMmvNIzX7q1Kno7OxEY2OjmOMh42b6yVBK3P6eD+gwjJgKiRnbIw0xRQvIDanMuBXjg36/35Lm8uCDD2LJkiXC8+eZ9wv0nd6XyfQh7BOJBM444wx12223WdKFhmumhUCAGsfFsdLSKSsrU/TqMLZuljgVEVRXV+Ovf/0r5syZo+mPAp+VDDk2h8Oh7/3RRx9hy5Ytlj1lgi+VUjrFix40fkYFLZ1Oo6SkRLHIT7bi1tjYiI6ODuFn3Of8Lefioosuwsknn4xvfvObuO6663Drrbfi4Ycfxh/+8AcsXLgQy5Ytw5YtW9DU1ITFixfje9/7HvLz8/UpkQDwxRdfoLm5WQtJ9pXPJo+aNGkS9uzZo92d7MtQ/R+qmfzTtMhypW+gD0OxadMmCx6Hzy4qKlJ79+6V/tPVGBvW/XvxxRdx0003idPp1LXKue5MHyVGgo39FBFMmTJF80IA2iO6YcMG/drELfB3pL2qqiqMGzcOhx9+OM4++2z813/9F7773e/il7/8ZU61yO12+6BjbM154OuamhrL6ZSjRo3SCinz4FlC1u12IxwO4/XXX0dXV5ew/yYd0Ern0cjbtm1Dc3OzDldwL5rKhVJKFx9jMz0+Jm8Kh8O45ZZbtMwyq+4B0MYV1+Wmm27CuHHj8OMf/xgLFy6UfXmnhms2dnj69OmDCDhXpg/0ubg4cXTtkqHRxT179ux9xuhN7XbMmDEaZGWiJM3/N27cmLNLjONIp9M679FkRBTsZGYU6Lm4z0i0fEb/cZgW5mASBjDAzAoLC3V8x2RwK1eu1IAr9nWoPNYFCxZg69at2Lx5M7Zv346enh4kk0lEIhHk5eWhpqZGA9CmTp2qNyXHlovbXUTQ1tamXXBUSkw8gBk/jcViliIi5jwyPpqtFOTS+pVGfT8qPKbrWES0y9c8ynK4NhTzZRpjNBrF7bffrgtgEAnbX2VQ0RrnGK+++mp1xhln7NNdvK/n9zMUyQZvsSwoFUbuCzY+t6ioCFdffTWWLFmC2bNn6zHQPQoMMGvel0wlHo/jj3/845DVIoEBYbt27VptxZDBmbFngoRMpmfObVtbG1paWizKgFnyU6k+YN/NN9+snn/+eTz//PPqwQcfVD/96U/VD37wA/XNb35TnXTSSaq+vl5VV1erkpISVVhYqEpKShAKhSwlRIG+Y1tJH1SUsuO/3Ovr1q2zINBzbdxD9fX1FhdzLuEeYMDrRqFm7iej9oaKxWI6Lm0qDQ6HAz//+c/x8ssvCz13FCAcs5l1ZCp5IoL6+vpBgDlgoPYD72fySgovt9uN8vJyixFH1zcPlMqlLVu2THv9SFfEZbDIU3V1tSU8UFZWBpvNpjN2+L5ppe/atQtvvvmmXg8TMEtav/LKK/WeopcsO3/exA5Mnz7dgmHJ9uwUFhYiEokgEAhgwYIF+POf/6zL0zIcAkCf6Ob1epFMJjF69Gj1y1/+Em63GzfffDNaWlrk3zmcRQv06upqS0fZwVwb3WOm5WTGwNPpNMaNG2cBFJjud8Zw7HY76uvrwcprZl9oRQF9VYYOxAICBtxb2YU/qBE7HA4kEgnLecHDtVQqhaKiIj2Wzs5O7Ran9Q8MaMVm2gzPmTfdTDabDTt27NDoVX5GhYOpciNHjlRz5syxxMV9Pp9Ggn7pS1/Cxx9/jBEjRqi8vDxMnTrVkppFAsulJRIJzYjJRICB+BrHN3bsWKTTaWzfvl1vUM470Fdu0owPHkhLpVLYvXu3RSkxQzCcx7q6Ovh8PhQXF+839YmNHibeKxwO6/sxBWjRokX485//DKBvfYLBoJhMrqWlRZ+45/P58OCDD1oYyP4a17anpwdtbW16fujmpGudiPT6+nqMHTsW9fX1OPLII3HJJZfg3nvvxaeffor7779flZaWqsLCQmXG/jlfDodDxzipdPavl/z1r3/VjJSN683f79q1S4PQTIuO68PX48eP15+ZwiIej6Ozs1PvAZM+ON9M32HKYDgcRjQatRwgY/IYoK86Yl5enlai+Hm2kKRlRmUQ6HPlKqXQ3NyslfPssMf+Gtd4zJgxlvoAuQp0NpN/mvuLaUzMu/Z6vVqIEGfkdrtx4YUXYtGiRcK0WPM0Q9MyNY0sm81mKZ9qprH2h0eEc5kNxORJmfTs8ntUIgHkZKGn02ldZ8IUyPyfAjUvL0+j8l0uF5jnnpeXp8MSLEXL43IB4I9//KOmWRqZmUxGe1bnzZunpk6dqmUDcRZm5gvnjh6w7LngfGYyGXR3dwPoE/QejwdXXHEFGhoaxOv1DjLs8vLydJy8t7cXp59+urr11lvxxRdf4K677jqg8tiWCU2n0+ju7pYpU6YIkPvBEsDAASnHHXecPs2MJ1bxNf82NzfL1KlTLQeC8PeFhYX6/cWLF4vIwJm4wWBQAxSYNjF27NgD6iOfc9ZZZ4nIwFnBIgOHibC4QygUQkVFRc7354EtHM+uXbtEpO9wEuYAc/OZp/js2bNHKisrBRh82t2yZcuE+dWxWEwjfU3U7/XXXy8AJBAIiMPhEKWU5OXlye9+9zshJoAWy/r16y1nhgO5n0bmcDjk+uuv1/fkIR1k4lynTZs2idvtlq9+9av6cAyumYigoaFBiouLLQcjHMj1/e9/X6gUmaflsTAHx1lVVSUXXHCBXHrppZqezbFmn1W9v8s8qObdd98VkQFQJteBp8+xb6lUCi+//LLlkBGbcQqZeV68zTiJ6he/+IU+PUpk4Bzs/+QyT4rLPgSEn51yyin6oA4Agw7Psdls+rM333xT0wHj86QD7nkeWMIzss15vvnmm/UBH2Z/WGWR88rXZvzXvMxDXUh3gPVQHtIs9zVpkTnOqVQKu3btkoKCAvnyl7+sT/RjGqjpTdrfxUM9pk2bNuRhR8NdSimZM2eOmOMkTzIPaIlEIjjssMP0M1wul2Vux4wZI59//rkQS0DXL/kN55jrlE6n0dzcLPX19UP2acmSJUIFOpPJWOiec3jVVVcNOhfdvEcu43c6nbJhwwYhn2PfqNj29PSgt7cXv/zlLwXoO9DInB8qesz6iUQiKCoq0qerffjhh8Ixi4g+LItzu2bNGrHZbHLuuefqU/qIIzH3Euly4sSJOa+xUkq+/OUvW9Y2e09ynSiTDj/8cLHb7fLHP/7RIgc5VvPky+zLBoCALjV69GgABwYKYdu7dy86OzuFv6MmQivV6XRixIgRqqyszJIHncn0VYXq7u5GMpnET3/6Uxx55JEqHA7rcqv5+fn6N+l0GqtWrRLmy+bSTCuhpaUF0WjUYj1To6SGaLfbMX78+JzGbxb6SKVSKC4utiC8eU9aOwQkMR3NrCtsti1btug+mu5C814807inpwepVAonn3wyVqxYgcsvv1y1tbUJEakAUFJSgpEjR1rmIteQRSaTwY4dO7Rb1AwFJBIJvdalpaUoKytDY2MjYrHYoIpmXq93UB9yabzPli1btNZPjwdzwimk6urqVGFhITZs2IArrrgCJSUleqOzBG48HrfUXt9fozent7cXjzzyCFpbW4XuQNOdSTceP5s7dy5+8pOfABiIs9HiZozeXPN0Oo3GxsZBbuH/tLndbnR2dgqfTY8OPT4vvPCCZJcbzrZMzZDCxo0bNRZDZCBn3mws20olx7zfjh07tGubLlO6/k33JgDNxMhYs4Ft9Oodcsghavz48eD53yYWhV4/06tjPqOkpERVVVWhsbFRVzm02WwHBGjknDJd1IxJD9fYl/b2drS1tel4L3kIaYyW+/jx4y1WrIlc37NnDy644AIN7u3p6bF47WiVm0C30tJSjdA3xy4i2Lp1q8UjxvcJKgOgc/lpTZpWeS4eTqCPvrZv365DWebvyG8YQgT6UtYIZsvG4/QrWEK6AfoquHk8HnR3dwvQBxg0T/2rqalR3/rWt7B48WKNpyH/Iv0AA7XmCdDLBQApIvjkk0/w0EMPaYXF7XZrpYrAOJ/Pp/E7CxYsQF5eHm644QYqq+ju7taIfc71UDRm4+S5XC5MmzYt546aHQb63HFNTU16kimMqJEwJW3cuHGw2fpOlXG73Rg5ciSCwSA8Hg9mzJiBG2+8UZEQzZgWNUuXy4V33nlHo2VzaSZYpaGhAU1NTULiMVNuuPGdTqeOrQ3XTLeq3W7HqFGj4PP5FD8zAR4kPjKznp4e7Q7KBuIsWbJEx9UZ7+IYuD5m7ezf/OY3eOWVV9S4ceMU0AegIoiO/0+YMEHf70CUtX4tFqFQSMiEGeviXwAoLi5WU6dOxY4dO/Tm4eYnmIwo0QMRVhzjqlWr9H05Z+l0WiuRBLKMHTsWK1euxPjx43HHHXcAgKVSFDCQIjlcM5WPd955B/fffz/8fr/OKc1kMpZ0QtJrVVWVuuKKKzBjxgwLEI1/Ta2a761Zs8biyv6fEOipVAper1cxVk6FcufOnWK32/HLX/5Spw1l05eplPM1U6zMlMxsA2Ds2LEYPXr0oDx0AATW6TWjJ8c8epnhNSrABMiaMX0qT0zpLCwstLhIgb5zJqi8m8hx3kNE4PF4MG3aNDQ2NmLv3r0AoNc2V1Al+zZr1iwLMvxA2p49e/SJdHT7UpjQmHE4HJg1a5YOV5qClv3evn07j/fV4RVanRy3y+XSiqjT6dTpswQhc73Wr1+v+yMiFgAq6f3QQw+FzWZDSUmJLrJyIAVRSD/Lli3TyguVomxlsK6uDna7HdXV1Rp/YeI4uLbhcFjTFAC89tpr2LVrlzgcDsVQoIlb8vv9uPzyy7XXlK5+zhcbf3v44Ycf0N6MxWK48cYbsW7dOo2iJ00mk0m9TgScjhgxQt1zzz1oaWnBfffdh3g8DpvNpgDroWlDCnRzYinQD8RC5yaJRCJoaGjQ73NBGNtipw8//HBkMhl9Vu/u3bsBAPPmzcOiRYv0EaNcXKZYmCUZWW4zVw3QFIZtbW3YuXOnjvFnI/Ip8A499NCc7h+LxSwlOuvr6+Hz+dDT02OJo3I+6KYE+gB0RKBmMw+eGMWWjX4FBo5unDFjBq644gpFFDbjRCQ6HoRzyCGHWIqAHEjbsWMH2tvb9e9orbvdbu32BvoAhR0dHdi1a5clxk4mwDSjXNcOGKDH5uZmnbJDBk3lyev16tzRuXPnIp1O46OPPsJ///d/qyOOOEK7mAFogWxiCvb3bDPd77e//S3eeustsdlslqMPKXyYcpLJZFBZWal+/OMf68/dbrfFgqGixjnduHEjwuGwLuhxIErXvhqfG4vF4Ha7sXfvXmGa3XXXXSfMDTeR6ybuw0x7UqoP6W7GSc3QC+m4qKhImTUWzLZjxw4wnSqT6Sucw4vWOuebwozzzGcQ1ezxeHTGwaxZs+ByuTSy22azYe/evWhpaRH2k2PIFriHHXYYIpGINkioJOTCtE0lgXni/46HMxwO6zg6BbmZJcIxMWZt7l9WdaOg/vjjj3H55ZfrDWYqXeYas3+0ODle0vSmTZsGgelM4ZnJZDBp0iTk5+ejqKhIe2ZMj2cuTUQ0v6PxRUWCioeIYOTIkYpAPPaTfMQU0uFw2ILtAYBf//rX8Pl8YLiI3irSbH19vTrrrLPw4Ycfwmaz6SpvJqCUr6dMmZIz/2IlxnQ6jZtvvlkrV1wr8mPKIofDgaamJrn22mvVlVdeiWeffRavvPKK5OfnDypHPhR92UyLoa6ublB98VwaJ23z5s0WC0OkLw5I9CUAHHfccfp3BQUFyM/Pxw033IDHH39c1wlubm7WG4paPDfr3//+d2Ghk1y1YPN7IqI3DjcKMFBLns9k6k0ujS633t5enHLKKdqCMT0dZo1nEhuVmaEYx7Zt2zSwDrCCRMh8CwoK1JgxY5BMJrFz505hzWKn04lQKKRTvNgP5mibLs9cGtfAVNhMBaRf+RIAWinkOc/mhgMGzi03GUouz2bbvXu3dvtxQ3LjEZhyzDHHwOFw6GI0d9xxh/bEmMwt26LbVzNP+4tEIrj88svR2NhoKc1IIc4+UWn5+te/rr7zne/Abrfr51G5NdPCgD4Fr6mp6d+y8PbVSJcejwddXV1Cxv/EE0/Iww8/rN2IJi2wX1QMzZSlnTt36hKwiURCW4uk+eyzG0yFloo/BSf3nBkzT6VSFqvdtJboJmb8PBaLobOzU8LhMObMmYNIJKLnnQJh48aNlvmg54O0mU6ndV/No0xzbSb6m/wzm+b310weyyyO7GaCsGpqagadU+B0Oi2hO5/PhyeeeAL33HOP0J1rpgSzv9zD48aN08qwOaZ169bp0qqmQWEq9SUlJYpV2yhoTYUw1/Fv2rRJCzo27icqvvn5+Rg1apR+jul5NeecwFa+73Q68dxzz2Hbtm1CEB3X3wSfXnTRRTo8x3sxFRsYSB+tqqo64NPQfD4f/vGPf+BHP/qROPrLR7vdbni9Xi0T/H4/otEoRowYoQDg+9//PkpLS3HHHXdgxYoVwlK0Zsgzu+ldnE6nMWrUKIwYMWLQZA/XuICrV68elPNINGEsFkMwGBQAREhi3rx5ePXVV3HfffepUaNGKQDYvXu3lJSUaPcCiwGwPf744wfsjsx2ITKtihYIhYsZLxk1ahQCgcCw92aKCNGXJ554oqWAgenC4vdJDEyTMOeL3+3q6sKOHTv0AROmK8/hcGiBVltbi3Xr1unT37q6uiSd7jt1ifckAUycOFEfgZjtztpfM9cXGBDm/fEq2O1959QD0BkCn3/+uQXVyb91dXWDDhDZXzO9JwB01ToKiFSqrzoVXbb9x+DCZrNh9+7diMfjmD9/vrrhhhssByAciJeCFiUZfUtLC2677TbNPDlHFGZM6eH9b7vtNo38BgboMRsFnEwm9Ryblt9/0qgMA4DL5VIFBQXqpZdekssvv9wiKE2lyWSi2S0ajWLbtm0WlK/JYPgb5jeboSZ+ZqY18ghi0yVOz1a2MkDFmQVj8vLyUFxcbCk1alrywAC9mOEBU0Ay9p2Xl4fPP/8cAMw6A8POL++ZyWRQUVGhqqur9X1z2V9mKIapc1TYzT6ylZSUYPTo0Xocpku+t7cXRUVFOm31Zz/7Gf7+979rD4WJhTDDKNXV1SguLtaf0XO1efNmna6p1MBhL6awtNvtmDRpErq6uvS+Jn0fiFLa2tqKhoYGjSFg42vS58yZMy1H65qhK343uxxwKpVCa2srXnnlFQB9RV1YZpdKRCaTQX19vQ5piIj29piKOgCUlZVZ5OT+Gg92Yirbb3/7WyxcuFD8fr/GC5jGFee0p6cHEyZMUL/+9a+xbds23H///QAGPML7mlubSVDl5eWKwINcmR0J127vKxAQj8cl+7csplJQUKAmTJig3njjDXzyySd4/fXX1UknnaR44EgymcTIkSMVGRDRfRS2ixYtkg8++ABTp07Vle0OpJGhfPHFFwiHw5YyiBwHBQjPic/1ng6HA9/97nf1CUB8z/wOhTGfa5YzzW6JREKXpDRBKFQeeB166KFwOp1Yv369zo0moRKkyPmcOHGihRBzERgmwG/FihVauTArewEDSsOYMWNQVVWllTun02k5xW/8+PEYOXLkvyWs7HY7PvroI3BMJkDK9AwVFRWhrKwMGzZs0K6122+/XZmFjejuHa5RmTS/W1hYiBdffBFPPvmkkHnw4BaiVoE+JhoOh1FdXa2uuOIKfV7CvmKznGOO439CoHMvNjY2itfrxbPPPivnnXeePk7XbKZlbqZRUpACffO2du1aC1gTsB74kkqlMHnyZMsZz/ye0+nE8uXLNQ3xCGK3222JldNy4vMpSLIBUO3t7dLU1CQNDQ0oLS210Kvdbsfq1at16M7EfJg8iqWvzbPRDwTnQUEXCAS04parBWcKx3Xr1iEUColZrpceEArJQCCgWDyJYDegjyZtNhu6uro0/iiZTOLJJ59ET0+PXh/SJrEUAFBRUaGLAVFJZp82bNgAYOiQDOexvr4era2tg/bTgQj0aDSqvSl8vrmvqbQcd9xxoNJk7hHTi2S63M0+vPjii2CRHq6/+fv8/Hw1fvx4xfRlPoN94f0rKioUz3zPpVFx7+npgdfrxUUXXYT29nbp7e3Vcshms+mjcru7uyUQCEBEcN5556lzzjkHf/3rX/Hkk0+K2d/9Wuh0WVZWVsLlcuVMzKbLiSfnsDFeYS50R0eHzJ8/X02ePFl1dHRId3e3FBYWKm7wUCiEdDqN9vZ2yc/P17nh0WgUr732GhKJBOrr6zFz5syc+sdman47duxAOBwW0w1uNoJlWMhgfy2Tyej41Q033KDjizzS1JxHat1K9dVfX7du3X6JorGxUfePjCqRSOhwhM1mw6GHHopkMon169drJkTlqLi4WBHgA/RtXLqrgNxciyZj59nFxBkAA4KViP2ysjI1ZswYtLS06PxQk0GUlJSo8vJySz394ebXfE3rLjv+TEUpkUigqKhITZs2DZs3b9YxVKfTiZtuuglKDZwQlQswjvRMK7L/THQ4nU5897vfxbZt24TFVrLHlEwm4fP5EIvF8L3vfU8dc8wxAAYsIJOZc65ZctP8/D9tkUgEY8aMUffdd59ccsklAAaUZbMvVMrMec1mHOl0Glu3btVpg7T66Krk3FZXV+t7m4zXZrNZjgulst7b24tgMCi7d++WTZs2yapVq2TZsmWyYMECeeGFF+TRRx+VW2+9VS699FI544wzZO7cuTJz5kyZPXs2Dj/8cJxzzjlob2/XY2CqWlNTk8Xzlj23iUQC+fn5GD16NFpbW7UX0QSUDtfMsENlZWXONQg4H2y7du3SCkU28Ml0+zIfmyHNsrIydHd36zWMRCLac7RkyRK9zuyXWYgpkUggEAigtrZWrx09WU6nUx+SZY6H1j77PnbsWA1INfEmB0K/6XRahyAZvmMfgAE+NGvWLH1WRXZmhEmL5j5kGHn16tVYvny5VjxZXImhHob3uJZMz+U4qVS43W7U1NTkJCPN8Agzovbu3YvrrrsOgUBAh99Mz0dhYaHqxyVJb28v7rnnHsTjcdx99926fOw+eScXljlwjz32WM75k+jPITT//9vf/iYkiuzY2FBXdn5p9sUiAUuXLpWioiJLziVf22w2cblc4nQ6de6hzcj79Xg8OjeVeZJLly4VgtT60bLahcz3H3jgAbHZbJKXl2fJKeaz+H9eXp4sWLBARPqOO2SMl1pxPB5HLBbTLpZ4PI4lS5boe5i5m4FAQACIz+eTiy66SKhxMs0hk8no16lUCk1NTQJAvva1r+k6ABSw/H4sFkMkEkFvby++9a1vDcozHu5irmsgEJCWlhY9PsYzzRoB6XQaN910k2WOKTyYtnHdddfpeTyQ5zPff/v27Za143jNHM1bb71VAMiiRYuEIEwRwW233abzY21GfrXL5bLkhOc6NxMmTBA+k0IkHo/rfSUi6O7ulmg0ip07d+rn8vfKyNV1OBwyatQoIc2b+5LrGY/H9frzMyrBnGPz/HMRwYcffijz5s0ToC8H/qmnnpIjjzxSP5/XUOPz+Xz6d/xbX18v5pyLCNra2sR8ZiQSwamnnioOh8OyzkopGTFihPzXf/2XXHPNNXLqqafKCSecILNmzZLa2lopKysTr9d7QGuwvysvL08aGxuF+8Dsn0lDt99+uwCQDz74QEzhYI5pqCudTuv1SCQS+MMf/qBpaF9zOhRt83rnnXc0/+RepwBlvv6f//znA9q7rOvBe5JuUqmUfv3oo49qGmTtBbvdLhdccIGY82XWFWF4ox+9LbNnz5Zp06ZZ6JtrMFTtiWw+dNVVV2ka4l4aTn6Y68R1+OUvf6nvTdrjPB9zzDEiIujo6LDwsf1dpkzgfL388ssHRIPZrwsKCuS+++7TtMa5ZI78nj17RETQ2toqIoJf/epXAkBuu+02EemrzTIUPTqoPVMTGj16tHaT5tJMl5qI6BKhfMBwbbjvFBQUqGQyienTp6tly5ZJU1MTfvOb3+D9998HAO3WoyWVTCaRn5+v457so+mSBfrS1xgvYQobG60HnqxjogpJbMBAWtoDDzyA+fPnKwLVeB+mCjFuHgwGhZ+/9dZbllQSNj4rEono8raMe7MPpkZaWFioRo0aJbt27cLLL78siUQC7e3t2L17t0ae79mzB62trYhGo9ixY4dGPOcKCjPnZceOHaisrEQikdCnU5Eg6YalS4xzTM2T2i7dkrmkBQGDy/82NjZi7Nix2htiAga55uzDpk2bcNJJJyEQCCCZTOLCCy/Ehx9+iH/+85/6N2Qc3Ad0r+UCTuvq6sIll1wiL730klJKaQAa6cRut6OgoEAlEgkUFhaqW265RX72s5+Bx7Jmz28kEkFraysKCgqQSCT0SU7Mi6W3i4eCALDMAa3ifte4PPbYY/jrX/+KPXv24JRTTsFVV12F008/XS1evFh4kND+GkMKpEsWUdm2bZsUFBSgq6sLDoeDdCUNDQ3o6OhANBrF4sWLNV0AA2mhLS0tWLBggeXQGCok/9ONnsPq6mrLWprAOIfDoa2+xsZG/btcLDATW+F0OjFq1CiNYM5lPOwTQ33knxRSZvlWPoOgrFx4dDqdxs6dO3HEEUdYwoPZY+R+McFyQN8eNq1xxtJNTMK4ceNUbW2t8Fx3Wr9sJj4D6Esh7enp0R4yjmXLli2aNzEE+u94qcwsGnoOOH/Lli3Dhx9+KHPmzMn5xuY6cs+VlZXpk96Ga+YYmBUUDAbxwAMPYO7cuXL00UcrKk2BQEDF43FUVFTo9GMAmD9/PmbPno0//vGPOOGEE+S4445TQ6UTWiw5EcG2bduksLBwUIWn4S673S52u12+/e1vC5kZ80r/k6u9vV1CoZD+n5uwublZ3nnnHTn99NMt1nJhYaEAsFjzpqZMa/umm26SdHqg4pGphfH1hg0btMWMfq2KFgv/f+ihh7SWZVpOtAbMqj4sZhCLxTB27FhdWc7sn6ndBgIB2bt3r9YkuWi8D7XlM88802JFIUvTBvo8KQ6HQ2bMmCGlpaU5V3Ey++RwOOSxxx4bUkOkF0JE8M9//lMAyI033mipWkXtdvHixeJ0Og/YCqPH5Re/+IVIv+fCdLGl02m9mRcvXix2u10uueQS/V329W9/+5uMHj1az0v2M9ivA6n29fTTT4tJR5FIRAtiWs60ZEeOHKnvzbnletjtdu3lYp/Ne5DhmpWvTHR3Q0ODPPHEE3LyySfrsSml5IILLpDGxkahJXPnnXdKSUlJzha6z+ezfGfkyJGDvmt+bu4bXi6XS1c03F81SrvdLk6nM2cPznC0+/DDD+txk9+RN5E2P/74Y3E6nXLNNdcIPx/OOjcv7s+mpiYZPXr0Ae0vk+5Ir7SATTcy+7p7926pqqrK+Rk33XST0GNIA4W0ytebN28etGZKKSkrK5O9e/eK6YUzPYTRaBSZTAannXbaIDrIy8sTj8cjfr9f6urqZN68efLlL39ZLrroIjErfdLzVlZWJk1NTULvLOdgOA8vZRf3ye23327hfR6Px0JvF1xwga4KmIsMomeR89eveMmIESNymn+TjulN5v/HHXecdHV1iTmnIqI90yZPo2fmnHPO2aeVrgmcVzqdxoQJE0QpNYjZ7e+iAnDYYYcJCTG7dN6/43I3N0wqldKuCBJXMBjEokWLtKsnEAhYmONQRO9wOGT+/PkWgW6WHSTjjEQiqKysHMR8Kioq5Prrr5c1a9bovrS0tOiSlnSTmApIOBzWTOKtt94asn9D9fXjjz+2bHCCXTi/oVAIv/rVr+T000+Xc889V6677jr5+c9/Ls8//7y89tprsnz5clm/fr20trbKnj17JBKJ4Cc/+YnY7fYDElgkxssvv1xM4WxuJgqzpqYmKSgokJNOOkkymYweN4m2tbVVSkpKchLoQyk73/rWt8TcwOZm4Nx0dHRIcXGxHHvssSIy4MoiU7z33nst9x2K1nMtUev1eqWyslI+++wzoYA1y4uyFjlp4cEHHxzk3jfnIlsAmcKb6Vrd3d3y2muvycsvvyxPPPGE/PCHP5TDDjvMoiA4nU6pr6+Xv/zlL8K1YuhhyZIl+nv7E+hDzUlRUZFmwj6fTyvz2Uqk2+3W5V/3xeT4W7vdPshV+z910W1slgmlV4br0tzcLOXl5XLccccN2ru5XCam4Kijjhq0prnsLYfDIYceeqgwXEkBQuFmlhc+4ogjcp6vL3/5y8IwkBkKpFubYaK6urpBew2AfPHFF2J64cyy3lRcFyxYIHfffbe88MIL8uabb8q7774rGzZskF27dklTU5OQX3R3d0soFMLNN9+sacB0va9evVqYlpiLQWjKCfKDa6+9Vs899zUVU7vdLkVFRbJ27VrJNSTMw5G4xolEArFYDLNnz855fZ1Op2Vv2Gw2yc/PFwDygx/8wMKjyNvJR0yD5ayzzhJgIDRk8mCLQDctvjPOOCNngZ4tkMrKyoQCLReBTg10X1ckEkFnZ6eOU4bDYUQiEUuNdA6GsVv2m/H07L7abDYZO3ashMNhPVk8eo/9JtETjHPzzTfLU089JStWrBCmhnHeeGQhmTnvwz52dHQIhY2I4JhjjhlyHk3mTmL//e9/bxHoBB+Z8Utqa9lWBRlDb2+vJhZT08tFYJnr63Q65fjjj9cZCSZD5FrTmpgxY4aMGTNGYrGYnhv+7e3tRX19fc61kM3XSik54ogjxKzlzo3MPrBPhx56qNTW1kpzc7Nkr2swGMTZZ59tET7mOHOdHwBSXFwsAOTkk0+W/vRMy3hNzT4cDqOjo0PmzJljYZzmXNDLxT3CWtam0tLS0iKjRo2y9MPlconX6xWHwyEjR46Uk046SRoaGqSrq0vMTZ9KpbB582ZtdQ8n0E08inkNZWXTus4WDA6H44AVyP+pa9asWcL5i8fjlj1kWqtHHHGEjB49Wjo6Oizztb/LxDbwvW9961t6PQ6Evm02m5SWlgqNFlOomXuOz8hVoB9yyCFCjAOPBjWr8lFAn3766Rbrmr+nQmie1WEq0rz4OfkUlRBzDPztiy++qL2TpuLz9ttvC4VZLgKX9zXX4KKLLtJza45DKaXXhLHoXOSTeW6BOe4LL7xw2Lk390j2WRq8HA6HvPTSSxKNRhGLxfT9TcwHFfGPP/5YlFJy8skn6zUxlT1LYRnGTg477DBL3miuTaTPctu6dav+P5ff7O9yOp0oKipSTB9KpVLCVBemizQ3N0tra6vce++9avHixToX2YxRmvEwEcHevXtZDtCS8wkMpCgAwHe+8x11xx13qHvuuUd9+9vfVjNnzlSBQECRGUSjUZ0ik0wmdfpSLBbTR/vl5+crxtr+9Kc/DRm7NOebhAxAp3KYiHkTsc+TlwKBgNYmeYwqzzLOy8uD3W5XFPCTJk2C3+/PKf5jNrvdjoaGBrS3t+t5I90oo7CIy+VCfX092trasGvXLjHjbpzf+vr6nOmDsXGRvhzNXbt26fPZTQSziFjW7tBDD8WuXbvQ0dEBAHo+4vE48vPzceutt+p4/lB4glzmx263o7OzEx6PB++++y7uv/9+4dkDZH6Mu5Fei4uL1UUXXQRg8KllAJi+pDMZzJx2WlXl5eWKiGhmWfBQpNLSUlxzzTVYtGiRqqysVIWFhUopha6uLgmHw7Db7aipqbHU8B5ujDz1zePxWI6ABPoyHYiqNmPhZi67eYwrK8OZ9SW4xtmXmaI51JVLa2pq0sVwTIuGjRk29fX16Orq0iVYc2nkU2ZaF6u55XIP8zv0oGzZssVyfzMPmnM+Y8aMnPnznj17NIKc9yJGJJvvZ+MGnE4nVq5caYlnm/H0aDSqMwPsdjtCoZCuyUGcB+eGHiegr0AO97YZs1+5cqUeVy7zpy1TowWDQf17cw+XlJTo+Xv11VfR3t4+7ASSp5h4G/aLlQH318jHAWsJXnNsSilcddVVaGxsFLfbDY/Hg/b2dl1Ipj8dGW1tbTJ37lx12WWXYeHChXjttdfE4XDoCouZTMaatkYgwuzZsy0HGgw3YLOl0+lB52Hvrw1noVNQEslLYRoOh8GKO1VVVaq8vFy1t7fLpEmT8NFHH+n8Pk6cKTBZXGTDhg06x5PgE46ZoBlWUIpEIvrEIm5glrUlMC+VSpmMV58bTca2fft2eeCBB/SmMXNVOV5zHgFgw4YNQwoWbgaWFqRA5VjoquNmCwQCOi2lpqZGVVVV5bQ+fA5bU1OTrhjHsZkMkv2eMWMG4vG4poXsfNIjjjgiZ1Cc+fx+j4cGL1FRMJkAv3/44Yejt7cXGzdu1BuZYD4RwcyZM9V1112nz0Pm73IFhAIDiiLp59e//jU+++wzIRiH46am7/F4EI1GccYZZ+DII48EYM23zcvLw5YtWzQKl7TGdCPSqM1mw9e//nXdB6ZZXXLJJVi0aBGuv/56lUr1pdkxVlpUVKSYwpNKpXSFtP01ApbMI0wJ9jPX3ARBkQbT6bROGaKnAoC2EqmQ8POh9v//ROvs7NQ0S8Wa80iG3U8PSCQS2LBhQ860SeFIOkin09ogMpn5vhp5nPn7tWvXAsAgRcEEw86cOfOACiNt3bpV38c8G4LPSKVSmD17tgXAyrZ8+XI9xlQqpcfGOgI86rbfkhWuGyufAdDfJ8iT9SjMNVZKDVlnYn/N/A77TkOO/2fXzPB4PFi3bh1efvnlYe9vKlNca6anzpgxY9jfc9zZKa0sE81+dnR04OabbwZg5T/0cAKA2+1WAHDVVVchEAjgoYcesiglNpttwELnQxKJhD5AJVcN0Kx+BvQhRbO14H214QQ60b35+fn60AS73a4R1n6/X7t4CgsLVWFhoZo2bZr64x//aBFGQy28iWjlJqfAJoqTFy0K5hUSmEZXvd1u1yf6pFIpFBYWKtagb21tFZvNhkcffRSrVq3S1aCGEhzm6VUOh4O5qcLvkig5D/25jcKNSSuKSkZhYaGiy5dKUX5+PiorKw+oeBBbKpWCedIdGXu2pTlmzBhkMhls27bNsg4kzlyK9rBlWzG9vb1obW3V7r3scZDh8MQ8em2CwaAw71MphWAwKFdeeaWipm3WX1Bq4Nz3/TU+v6enB5lMBu3t7bj11lt1PrTH49GZDmbO/MiRI9VXvvIV/SzOGy3+7u5u7RYmg6JC4HQ6EYvF9BnckUgExcXFuPPOO/HII4+oqVOnKlaUonBlf+j+9Hg8OZU3Jm2zEAmVQhaBAQY8SmaxEn6PHiQKLqWUrsDIPGN6nzhHfH0gJVj31xKJBPbs2aOZMxV9uq8BUNGFiGDXrl05CWPA6i2jYjZ27FgLsnq4ls0/GxoatIfHpA16MYA+C/dAEODNzc0Ww4D3NZWGcePGWQQ6jant27dbFFPG0+39VTIBoKenR1wuF0pKSpTNZtOGhClMSRuhUEh4WJQ5LhHB2rVrNW/LVaHLljPZ59qThjs7Oy3vP/3008Pe2/RMkNdRscmlTgmfT2Wc1ecSib6jrd1ut67V8tZbb+Gpp56STCaD0tJSxbK9ZqW59vZ2qa+vV9/4xjewePFirFy5Ulfz05NBnz2JKBaLobq6Wsd1houvAdYY4AknnCBmTMM8RpFxiex4rxmzMvPC2T/GDjs7O8VENvL+jLnwdz09Pbj77rt17ALAIKDE+eefL4y7Mr/XvLcZazPjOVygZDJpiXOIDAAbOGaCQd544w0BYEHJ55oPvnnzZuGZ27SQzHkz+8YNx/fMnFPTDXzjjTdKQUHBsM9mDIrrq5SSW2+91RLDJuCGiNd4PM7TvDQgiWvP32zatEkKCgoGnRluxtP2BSpyOBw6Z5WxJdIt1zAej6O1tVVcLpeON3F+RKzZB7t27ZLZs2drBDafnUuM0vyNGTt+8sknhXNNOuCcESjX0dEhpaWlgwCKXq9XHn30UU1HJr6F9JbJZFjWU2bMmCGffvqpmHuG627uazNmHIlE8Oqrr+6XFs185GwekJ0JYLfbJS8vT5RSOvd8uPj8cLyF9Hb++efLu+++q4FEzgM4c5zreO2112qPB2mE8VfS5aZNm6S8vFy+/vWviwnI6unpQUtLi2zdulVWr14tH374obz22mvy9NNPy/333y/f+9735KKLLpJPPvlECMqcNm1aTmM0sx2IVTj++OP12g913nwmk0E4HMakSZMs9zZj9tnrc/bZZ4u5R0gXZgW5jo4OqampGXSP0tJSWbZsmZ4/8kX2i/zG5Ml8j3uA80KeGY1Gce211w6aC6fTKWvXrtX7NZf4uYmNSaVSGDlypJ7P4ejjjTfe0M8a6sx0jmMo/p9IJDBp0iQBrFkdbrc7Z0A5148yqqCgQHbs2KHXivvdxNEkk0msWrVKAGgQp1bOAGsVIGrf48eP14co7K+Z2iNbf5UwfcpTthZLd/6GDRtk+/btaGxsxJo1a9Dc3Ayn04ny8nLU1taitrYWU6ZMQU1NjaJmVFRUpHp6ehAIBNDd3S1+v1+J9J3Hy3uL9B2J9/Wvfx1vvPEGli5daomtss9bt27VGpfIQKzE1MrMRq3W/Iyna9Hy9fl8OraZTCbh9/vx6quvyrnnngtgIK/XbrfnVKnMZrOhsbHRYk2ROVOL5mLyM9M1Z5ZepUbtdDpRU1OjY037a/QMmPdvaGgYNJfUsk0PyqhRo7Bx40ZtJcbjcbhcLn1EYEVFhdCCA4YvFcm1SaVSaGpq0jgBjjnb9e5yuVR1dbWQHmmdA9ZSrgUFBeqxxx6TU089Fe3t7bqOQS6NVhhzUlnT4P7778dpp50mlZWVKhqNatczrXWHw4Hi4mJ1ySWXyAMPPKD7DPSVwWSdf84xLTOO3+FwoLKyEm63G1VVVZg1a5ay2+1a6zfHasY+uf55eXm65j2VMNJ/Xl6ezt8l7oLeCypvDocD5eXlyGQy2Lt3r6ZHzkm2i5aWOHPpeT736NGj4fV6UVRUhPLyclRWVqKiogJFRUXweDzIz8/H2LFjUV5erqZMmSLr16+3PGe4xn3Q0NCgrUpafzabDaFQSLxer7Lb7SgrK0NhYSEWLlyICy64QNrb29He3s44MYLB4KD6+8BAXnVNTY2uYFlTU5NT6NHkixQaLS0tmreZ3iczfOjz+TBhwgRs2rRJ81eWheaYzdbc3KxDJea80TLOZDJwu91q3LhxwvAEeWM4HEZnZyd6e3sRj8clLy9Pcb+bSrrJAzg2KtHZVq6z/9hWM9xEBaCxsRFTpkzR+/tAGhWHXNtTTz2F0047DT09PVJYWKioFPv9fmXGy8lXOP8c76RJk7BhwwbtiSorK0NbW5vGjAzXF3pqieGJxWK488478cwzzyCdHjimlmFCtpEjR+Kb3/wmXnnlFXz00UdyzDHHqEQiAYt1aaYA/OhHP8pJwxzKOvF4PLJ69Wqhtch7UwvKZDJobW2V+++/f0it3fzf5/PJmWeeKa+++qpQc0okElqDykY69vb2WqwTVr5jmo3ZV7PymVn5yNTChkurS6f7qtmZOYFmCssHH3yg0chlZWW6D0xZyOX6yU9+IoxT8tjZaDQ6CGVqWuemJU/rMBaL6YpemzdvtszJ/i6uCf/OmDFDI6dTqYE0GPOwgXA4jNNOO00CgYA0NTWJaRmxv1/5ylc0yjV73Yey7EwamzBhguzZs0e49tmeFGrQ3/jGN6SwsFDWrVs3yLNjWtAigjvuuEOUUpZqg7mukVn1j1YR08+Y9SEykF9KWlu5cqXFC8Lfzp8/X0wrysyiIH11dHTIiBEjZM6cOfr+rFTIPWc+i7/n6z179siECRMG7eXs/cfXZtbIhRdeKJs3b5ZjjjlGamtrZcKECfKlL31Jzj33XLn00kvl+uuvl5/97Gfy29/+Vp599ll5/fXX5Z///KesWbNGmpubLR4es7+kW7OuAT9jrjPrNxxIWlhtba1Gr5shM/P5PT09uqYDrV2llBQUFMj48ePliCOOkFNPPVUuvvhiueGGG+Tuu++W3/72t/Lwww+Lw+GQ0047Taeu3nvvvbr6YC780/Q2uN1uWblypZieNtKq6d288847B/02e174WXl5ubb8TAudigDvff311wsw2Dv2zDPPiElTBN9yv2d7CLkHGQM2vYvBYBCJRAL/+Mc/BlURBCD33nuv7meuFjr3cUdHh5SVleWcimmz2eTTTz8V8jACR6XfQ0I5MNRz0+k0br/9dks9EdPTlQt97us77733nrAPHBuBhfz/X//6lwB9FfZI05a0NZMp/+lPf9IDHs7lbn5OJvjaa6+JycCySweK9KWEcTLMgh52u93itigpKREActRRR0ljY6PQzUphnE6nde44n8VD7hsaGqSqqsoiDMzX//rXv0RE9Pc5ByTwbGGRHeM3Cy5w8plC9vjjj+uUJlN4HkgOuNvtlrPOOkszaZMhm2Pt6enRzJ79YP6kqWywv42NjTm53E3mxrXNz8+XhoYGnWNuunRJS4lEArfccosAkPfff1/MPhOpTYaUzYSUUoMKrmSvm9fr1a45k+j5P9fywQcfFADy1ltvaVoxU8BIM8Q+nHvuuQL0KXu5pK2Z+aXZm3PUqFHS1NSkGYQJqqRyG41GceKJJ+qxMiebCgtxJGbZYPY/k8ngpJNOEo/HIxs3bhSRAZe8KcTN1+a90uk05s2bJ26321Ki1eFwiNvtFpfLJQUFBTJp0iQ57LDDpL6+Xr7yla/IM888I2ap187OTp3Kyfk1U6xMoUy6ZKoN6xfwN9lKF92OsVhMl/P1eDw5pxRyXvPy8mTTpk26zxRqFBr8f8WKFbJgwQL55z//KZs2bZKdO3dKZ2enmGNhv02FY9KkSTJt2jSdzsrU0FwFejaDpwFDwUhlJ5FIaEH6yiuvDLlfuE9IS+wHeR1TazmmWCymaer555+39IP3uuCCCySVSg0qZhKNRi350tlGkJnWmq1oBoNB+Hw+HbYiT/yv//ovPfZcBToVxIaGBl1ULBeBDgykiZqhXvKwoYS6KQsYtvrOd74jlZWVFv6UK30Old74pS99Sc+BOecmD4lGozj00EOlpqZGtm3bJiIygHLPdjHX1tbmnHYhIvp/ugh4So/dblfZiFF+f+rUqRamyt8aiEn4/X7t/ly8eDGmTJmC9957T8LhMCoqKpQJbCFAg8CDVCqF6upq9bWvfU27jrPTJHhuN8FvdA/xvexmErP5HS5yXl4e8vPz0dXVJR999BE6Ozvxwx/+EKeccgqcTif8fr/WunNpiUQCjY2Nemwm6j4cDuvTqvx+P7xeL1wul3ap0r2dn5+vi9C0tbXJmjVrpLGxEVOnTs2pD+Y8EAC2c+dOiyuXa0BCV0rp+2/YsMHieiJBEhRDxcl8Hu9notZN92R/bXT9fnb8jr+fNGkSAFjOxTbnnkBIoM8dePfdd+uysrmkrZFB8TXbqFGj0NTUhFdeeQU+n08rfwwRkIF4PB7813/9l+Ve/YwJjY2NmiY5JgLP6A485phjEIvFtIue6OBswCX7ZoIubTYbzj33XFxxxRW49dZb8atf/QrPPvss3nnnHXz66adYs2YNdu7cqT7++GP861//UitXrlSvv/66uvjii1VhYaEiVsHr9arCwkLtHiZgjv3g/NCd73K5dKoNU+7Mk9bMNXW73fpiOhitvlxBYQw1EQTL+wMDp46RJqZNm6ZOPPFENWvWLHXIIYeo6upqVVRUpMxwD+9pt9uxd+9eAfrOyN69ezcaGhokGo1i/Pj/T3tvHl5Vee2Pf/bJyZmHzGFIQiAQ5jAIOABqnYpWrUOrrXZUcR4eB6q1l6K1ouW21Nvb6hW1X0Wrtlrn2kK1VeusDCKDAoFAIMQEMp/kJDk56/fH4fNm7Z2EHKq9vfpjPc9+CMk5e7/7fde73jV81lplJsxyIHK+A13MuvMYeZqgQo59zJgxfZoUkbd4bw1mI48QEKi/Q8BaWVmZLTzpdrsRjUaxfv16A/wlscxxIBCA3+838ka73YFUCKmqqkrWrl0rf//73+WZZ56Rhx9+WB5//HHRcoHf2bx5M2KxWNoNwvSz2FmO7zUYud1uPPnkk9i0aZMQGA3AhIoHo3HjxsGyLGRnZ+OnP/2puaduijUY6f1B4Onf/vY3vPDCC8L/U0YSQNnR0QG/34/vfve7qKqqMqXQ3c6HcjGHDRuGwsJC1NXVHXAwzu+TudasWQOgt9MNIfqMF0QiEZSWlmL48OGoqanpk/ri9XptCN9AIIB4PI62tjZccMEFeO6552TatGkWBQHj9YyNcSJ6enpwxhln4Fe/+pUZr97MH374oUGx829kJGf8gwyiGUUf7Doum5+fb1166aXy5JNP4o033sCQIUOMpsdn+/3+QWO1RKdv3rxZysrKLL/fb2LiHR0dsnXrVrS3t6OhoQH19fXmamhoQHt7u0Hsbtu2zaA82aaP+dkHIp2+mEwmTaz4gw8+wNy5c22KkHNuysvL4XK5sHHjRsMnsVjMKFylpaUIhUKmSh+fp4US10PHaEnr1q3DvHnzAPRVLEklJSXweDz44IMPzOe4voylejweeDweq62tDWVlZdbSpUvlzDPPHHRugF7lIBgMmj7UXV1d2LVrFzIyMrB8+XJccMEFCIfD5rlso8jc7blz5yIQCJiaBhrJT6IQpsBsb2+H3+832QLbt28H0Nv/nMoxv8t5pBCnQnDJJZdYLS0tppe1U5FtaGiQnJwci+/KmDPrdetccgAmhk+FmgqEts6o8GnUto4PayWuq6u3+1VxcbGZZ2t/FkI6Shd584MPPsCJJ55oDjB9ELIevtvtRigUsglQ7Ykj/oHvlJ2dbQGpNMzXX38dH330EUaNGoWRI0di5MiRpgXyQOTkWT5j7dq14JxTSeM7U86UlJSgqKjIpOSRdNxXf2/VqlWYP39+vwh8KjWU+8xk4d7cn64qrHfQ1dWF5uZm7Nu3j8W/UFdXh08++QR1dXWmnn91dbXxErrdbrS3t5sDj2PVcXwg1XVu165dMnbs2IOut97a2pq2scT5bWlpwcMPP4zFixebmDbPFd5L7wnOqYigpKTEGjZsmLz11ltYsmSJ9cwzz8iKFSuQSCTS6pehlUkq+11dXQgEAli6dCnmzZsHn8/XB4vA9z3uuOMAACtWrMD8+fNhc8PqGHI8HsfcuXPTRrlDuTh8Pp+MHj1a2traTPcnurCJkCSzTZs2zbh3WEYSsMeFsrOzjetHV4FjTEyj6Rmn1SVdm5qaTBxbj9ftdsvs2bMNMpUWh4i9lrIzLku3oY4D8sCpra21Veb6f//v/wnQ2x2OSN2DqZpFt/sZZ5whX/7yl6W8vFyKi4v7IJPptmJ3I+ec8TP9/X6w9dXZDC6Xy3SC4xzp7kicC9YtP/bYY03MVOMb9uzZI6NGjeoX34B+3H5OvjjzzDNNBT7tEtOusoaGBiktLZXDDjtMWPtdW/LODlx0vX7/+98/KJwD3yEzM9OMlyh+loTlHLS0tNjcZrFYDKwcp9327MbE72l3OcNO77zzjrjdbrnwwgslkUj0Cck4kcj694zJNTY2mnLNnB9dCrm1tdUWm9eXMzylv5fO5fSuOC99r71798qkSZP67OMDXTo745xzzjF7XSO8ya90o/PvLASiK51xzJQ1sVgMyWQSjz/+uGRmZhrcREtLC2bOnJkWkr8/XhozZoy0tLTYqiA6w6I9PT04+eSTzftxTvqLS/t8Ppk2bZqJFet1Is+wUtmcOXNsFeOInaioqJBx48ZJNBodUHZwHP1lP+mMCP0dHWrl+P/yl78Y+TKYy12HTF5++eWD7hHh9Xpl9OjRsnv3buE8OOW687k61Dh16lQpLy+XyspKqayslHHjxh2Uy13LWi073G63yS4gv+py14lEAm1tbTj66KOluLhYtm/fLi6g18rQpr/X6zUdeA5E1BS09m9ZFqqqqtDe3i50l9Fa5ucodEeNGmW0fKLFeT8gZak3NjaaMdLSb2trw8033wzLstDW1iZAb7I+0bqkaDRq8V20hZZIJFBVVWU2MReLc3Agl7uOo3PBXS6XyYUnkOnb3/62de6552LHjh2IRCJE1iKRSCArK2vQ+bWsVAevZ599Fs888wxWrlyJzZs3Y9++fSafXbvOdKyN3gDtFdDutXSIWjMtPro6mV/O5/Jz1Lg5F8XFxaiqqjJrxOpHnZ2dGDJkiJWTk2PzbGjSOeEkva4ff/yxeR/9TuSxZDKJ7Oxsq6CgAHv27DH5+iRq0YxLkQ9FBMuWLUu7+A4AY3mwWiCQqliVTCaxZcsWGwI5GAyaQzWZTCIQCGDWrFkAUnuQbtd33nmnj8fI6aWYMGGC5fF4zHrQJUo0Oj+vvSj8v8vlgt/vR1ZWlqUR6Zxn1/7KZKFQCNnZ2RYPPR6KtGg5Z679Ve34njr+6+QV/l9b4/rvvPR6e71eKzs7+6By1DVfbNmyxbyjrnRIz4ju305LWFuQ3FuUY36/34xj/PjxSCQSxrIOh8M44ogj0hoj0DcUsmPHDsRiMdGeFg08o0u3tLTUjFm/s/Z48O9bt261WcZO7wBDHMOHDzfvxUPE6/Vi3bp12Llzp8EIMVzhrAOh5YG2lvkuzu9xjfj/rq6utLyH/dHBoty5L7Zu3Yo///nP5nf0JDvnke+n/50zZw4qKyvR2tqKUaNGWT/+8Y9tRZMGo2g0atzqyWQqg6S5uRmWZWHVqlXGS+3Mze/q6kIwGMTRRx+N6upqrF69OhVDp2uA6Spc7FmzZvU5AJxuMhExZUd7elIVrBjj0vFpbmJdjMLj8eCEE05Aa2sr3Z5m4BREZEDtUuGG/O1vf4uqqirJzc01xVOIovf5fGbjdXZ24sQTTzTP1UKxpqYGW7ZsEQAmrkd3JDe/iOCJJ56Qb33rW3LKKadIXl6ezJgxQ5qbm4Xvp+PudJMxRenOO+9EaWlpH/e6s31mf0QFgvPNf3UuLceuXZsk3WKTB2dGRgYWLVqEb3zjGyau6SQtaJlyxrVLJpNMMxSmtbBAAl1MPT09cLvdmDZtGqqrq01bSCogHOfcuXNN+pxOseNzAHsJVh6MlmVh06ZN2LVrl/B3XDcKavLpnDlzUFNTYxQACmcqOOQ9bibe/8477zRzoZUOl8tl3OWce5b5BVI8ykPH4/Fg1apVZo5bWlqEhyX/3tnZiSOPPNLMN4tQvPfee0Zpo8Kh49Q9PalKbJMnT8aqVauMN4xjdK6tPgSdQljzCu8P2NP7eIgxJYf383g8tvtx/pim5jxgBlKW9d+1Uk4Lle9KOZMOsRSxiGDDhg2orq42m4PrzzAWQwQAbDxEnnGpQji0cqm4jBo1yho5ciS2bdtmvBpTp061uUn1O+o1YKtcPoMA3XXr1tlSDVksiIqOZVmm2qC+X3t7e59Dkl7FDRs2COfE6cali/lLX/qSDYOhw6E6tqyzWvheznK/mnTbZqdiyvvxOytXrkx7fXW66r59+w7IX07Sh+Ty5cvR3NwsrOxGo8ApU3W4qL29HUcddRR6enorpJ555pnWpZdeansGAGMwOLEIRP1zT3G/dHd347nnnjO4Kx2O8/v9Rt6fdNJJ8Hq9WLFiBVwcqD6kucjU/pwAFKdmpeMMWoPYsWOHEZ5cbGr2/G5JSQkAGDcX73Mg0lr++++/b36mZ4Hv1N3dbeLpBQUFZuwcP5mP49QLwI3c3NwsdK88//zzePXVV9HW1oaNGzfiqquuMnmarNhFisfjRuhnZWXh5z//uRH6XMx0Yj0s+8n5cjIXDyIKBDIjyzLquWxra8PEiRPx7LPP4pZbbrEmT55stML+5thJOk7e0dFh6hRwbKzARQEsIhg9ejQA2Po8cx0sy8LIkSP7PLO/ePxAtGvXLqMIaKUK6OWjsWPHwrIsU8FQ/80Zq83c37e+u7sbX/3qV60LL7wQQEqo0fuTTCZta01vE6084j/o/uc7ivTmt1L5oSWYl5dnU3jptmXN+v6sKfL9mDFj0N3djaqqKuOBOBgvzOeB+F56LtMl1tO3LAsNDQ1G1jjBkf09U4eQGF7g91iTnniYcDiMDz/8EIlEAuFwGKNHjzYyT9/f6XnSXkG9Fwn6ZNjEqZAlk0mDEdH30fNDzyGfVVVVZTvs9d+o0A4dOtQmIylPNG9qotJH2cTDhvJcK2b9yTz9zuTpXbt2pSUfNYgSgC01Nh3SBt6aNWvw0ksvISMjA7FYLC0vkN/vNxUb169fb2qwX3HFFZgyZYrxxgF2w0TXIOEc87Cm8uR2u7F9+3Yb/wQCAfNZIMVDkUgElmWlFEDtZgfsQfqJEycaUJteTKfg5cLxM3z46tWrzecoaCnoqImNGzfOuGH1IjgFv5N4qL/55pt9tEwnUMblckE3onBavHSTOZHWQMrN5/P5MGHCBBO34Jw8++yzuOeeeyQSiaChoUFCoZCtEAA122g0ap199tnWZZddZnt+OqRjRXrctO6oyBAYyHhLd3e3cQMDqTVasGABXnvtNcyePdsCgMmTJ5vx6A08EOlxd3d3G9SsFhB0O3NNpk2bhoyMDKxatco2x5zfqVOn9hEUHM9ArjM9jg8//BAul8sob/pdgBQfzZw5E5mZmVizZo1Nu+7pSdW6pyXLmHo0GrUIRFm4cCGGDBlicCUUej6fz9THpzXOwivckARmHnbYYSYdht4nekw4H1Q4ORcMzWzdutXsL+fBwL04a9YsxONxrF271txfWy2fd9LvTH5J99142HAvvvfeeyaVilYYQzb64NHuT4/HY5D25BvuydbWVjQ2NkpGRgaOP/54NDc3491335XGxkYpLS1Fbm4uAPu+0gco0Cvo+Xz+S2AxQyd8F4YNE4kEysvLEY1Gzb214k+i1ymZTGL16tVmj2ojjnMCpJDuvKcTo6INQH0I8Xf7wbo2BUR7dbXx5zxHtGd2y5YtqK+vH3SR9ZoBvc2q9N8ORNyTbrcbra2tePjhhwGkenGk8/1EIoExY8ZARPDuu+8iLy/PAoDRo0db8+fPN+Fc7a2gta0VMfIaFUN6SzSAVxd/isfj5p6jRo2yhg8fjp07d/Za6CSt2RcXF1u0oJ0DAnoXQmuYQC/DUohr4UwhxMN92LBh1tChQ829uWH6S2fQxE23fft2I0ypFHR1dZmYOsfKjaUVB/7LDj86VsaxkCnLy8utvLy8Pu/9gx/8AFVVVRIMBi1nzJqbkPNz/fXXY+LEiaZiWjrEMIB2W1LTZZycwodlImmdB4NBtLe3Y8SIEXjyySexZMkSKycnx/J4PGhvb0dFRQUCgYAtTquZ2LnhuBYcy6ZNm8zGJVPSEuL9Ro8ejWAwaJQmp2VcXl5uantr4loMROQjotcBu2KhXetjxoyx/H4/1q1bZ+Mly7IQjUbNL2hhiwjT4uS2227Dnj17jADLysoyhzsBflrAh0IhE3sFgPnz52P69OkWPS18DhVVhii4ubViCqRwAlwfHujkVR5CbBKxZs0aBAKBfpG5n1dyehDHjRtnQnzpkFau/H4/KisrjQXJ+DDrAejiUkBq/phfzedREaMiFw6HDdKd3oPdu3cjOzvbKiwstOjl1PvKmebolJ/8l01RaOXzwOP3enp6MGTIEBvWQ1us2rVPpYD70KnE6/8PGzYMQ4YMAdC//OH9mVZMj6M+3IFeFzM9tCSna14f6KSamhrU1tb2u6b9Eceuw5jp8r/eMytXrsRHH30kfr/fSjdtNT8/3yovL8eGDRvQ3t6Ouro68Xg8uOKKK6w5c+YgFAoZhSgjIwNer9d4VbOysmzNvTo6Oky3TJ/Ph3nz5hlZQVlDXsjIyEB7ezuCwSAikQjq6+vh0vFK7a7jYcQ83nSABk4BvHnzZrS2tgoZkkzGZ3Z3d8Pr9RqXqPM+Tiboj+j61O5Wa/+HncJvINqyZYuJDdHCpebGSQuHwxg3bhwAGMCDSKogzQ9/+ENqeML3YvoQ/9/d3Y3i4mJryZIlJi1mMKLQcQKLNOn107Gt9vZ2xONxfPWrX8Xbb7+NM844w9LIVnZJYkMKpzKmx+C0nKntr1u3zmiWOp6vwxrZ2dnWsGHDDPiQG5n3zc3NNUqjfn66FtimTZtsHh9NPNRDoRCGDh2KyspKs3Y6DksFgCAYy7Lw17/+Vb7zne/ggQceMPNcVFSE8847D3PnzsXEiRONpeNOlXGF3+83GR1Tp07FNddcg+uvv95Yd0zbYV603nNMKXQeYLp9rlNIc+1LS0uRmZmJjz76yGYNfRFIe+p6enqQn59vDR8+3GBwBiO3241AIGCqlr3zzjsAejEezKNmEwymK7GWQzAYhMfjQUtLi1RVVckHH3wgr776qjz77LPy2GOPyc9//nO54YYb5KKLLpK///3vAIBXXnnFuOiZVqgVEB0W4v/1v6StW7eisbFRtILoDBNYloVx48b1sVT5d+fvKisrDbaH96NiyL0ZCoUsthXWFTM5bu3NaGtrs6VmZWRkmIOcrVS5jk6chf5XE70PTMVMh6gA6/z1dGUIx0/817333mu6wg1G5MFp06YhFothy5YtUlBQYLW0tMiTTz4pn3zyiVEyiHuh0hMIBNDU1GQMQPIiyev1mhoVxIXRgKNSoDEpPT09cPcnyF0ul1noiooKPPnkk7biGxSGznguNx//bWhowPbt2zF16lQTU9H+fx7yM2bMwIoVK/qg+IDBF4VFU/QhQnepfi+C6XjpxdqzZw9qa2slEolYPGg08ISHxaRJk/Dmm2+acTFe9Pjjj2Py5Mly8803W52dnTbE6v7CG2Af6lNOOcW69tpr5bbbbuv3fZwMzjE4gUL68zykKPSAVOvQU045BYsWLbLYW5utOwOBgHGxVVRUYOPGjX3WkFa3VpS09wJI9e1mDWQ9pzpm5PP5MH78ePztb3/Dzp07TW4prYaMjAxMmTIFa9asMUoUrV09HwPxwfbt27Fnzx4pLi62nLzDMYukWmM+99xzqKqqksmTJ1t0S3M+mpubhdb6D3/4Q7nzzjthWZZBPsfjcYwYMQK/+c1vLM5TdXW1tLe3Y9euXaipqUEikUA0GkVRURFGjRqFgoICC4ApAqHHRV6n0kd3uXMPrV+/3iiH2ouk+SA3N9cqKyuT7du3o7W11RSy+SId7FzLzMxMTJw4EevXr0/r/XQBFQCoq6vDxo0bxe12IxwOY+PGjYjFYmhsbER9fT3q6upsdRx27NiBtrY26BQyjRVi7f5kMolQKISMjAwTpw+FQqZFrTNOrj2KeqxA79o3NjaisrISeXl5toPRGYOfOXMmnnjiiT4YJpLes/X19dixY4eMHTvWYvyYLnnuPcr9F154wbb/tDHG5xC/4nK5jEeQWQOzZs3C5s2b0dDQYDx5znfWPzv3/Nq1a/HVr3510DWm3ABSCsaBlAUnZWRk2EJamZmZePjhh3HTTTdJQUFBH5niJF0kK5lM9TXo6OjAokWLTO0TICVPTz31VDQ1NWH9+vWoq6sz6+33+xEKhUzaLACcffbZuPjiizFlyhSro6PDKFw6q4l4LJY7D4fDqeYsOlWGE8HJqKioMC/LCdDamo53Ukhql/wHH3yAiooKZGZmmhiAc5IOO+wwm5bK+J/T1autRdLEiRNthw1jE0Ttcyy1tbVmE1Eo8v5NTU3Ytm0bSktLbe4NWm383vjx482G4gFGcMOtt96KU089VejeBWAD0OiNcfXVV2Pt2rV4/vnnbe8yECM6kfn9EccdjUZx+OGH48orr8Rpp51mNTY2SnZ2ttXW1maqyVHjDoVCqKiowOOPP97nICGAj/Og15VUU1OD6upqZGVl2f5OnqCwmDx5Ml544QVs2rQJo0ePtgkFEcGMGTPw4IMP9rFcnD/3d6jX1taiqqoKxcXFpqEIP09eSyaTmDt3Lh577DF88MEHmDx5srkXla2srCzr/ffflwsvvBDr1q1DTk4OGhoabNYHD99YLAaXy4URI0ZYQCpsoGOGJIVOFp/PZ2mvjcY/JBIJvPzyy8YjA/QK9/Xr16OtrU3C4bBNuJCPWdxlypQp+NOf/mRTWL4IpK0t7tvp06fj97//fdoFROjN6OnpQWVlJU477TTs2LHD5qVxhjH4OxYK0q5kzYednZ0YPnw46urq0NbWhtGjR+OUU04xhWoqKipse4MYH72WTplKyyuZTGL9+vU4/PDDAfR6lXQ4igYR+aE/hVwjpGOxGDZt2oTy8nLbftbeH8uyMGXKFNv9eD444+habmRlZWH48OEIBAI488wzcdVVV2HOnDkmTbc/rwRJGwRUKt5+++201pZypKenx9Rn4N8G2wdOWZuZmYl9+/bhoYcewg9+8INBn9/d3Y1gMIijjjoKHR0duPvuu7F06VJZsWIFgN6mTW63GwsWLMARRxxhdXZ24uOPP5bdu3cjFoth9+7d2Lt3L8LhMMrKylBeXo7S0lJLhyLJI5QxPN9EBNu2bZPq6moUFxenDnSt9dGFyC+OGDGi34o52jrX2qZT8G/bts2WDsLn8V4ulwujRo2yTZJ20ZOUO93GDBMnTjQxCApQfl4v6L59+2yWtx4HkDoYdGoQD1ECm4BUrql+Py4Wq3xdcMEF+Mc//gEgJfRZDnHfvn2Sm5trMY6Sl5dn3XLLLbJixYpBK10NGzbMgLAikQjy8vKQn5+P3NxcBINBZGVloaSkBDk5OQCAvLw8EzPu6uoy+cN05dAiZcyb+fk6Lsc10L/nnGjh2N3djd27d2PChAk2JZAxMv6/pKQEiUTCVK3TB7rL5QLde06PT38VrZzU3d1tKqrplDcngpeWUmVlpU0IiaTywm+//Xb5yU9+gp6eVNc8usD5rsx/plXNg14DD1mQQufZdnV1gZXWmC7E9B0i81taWuT999/vN1uE3b6cIRfiKDo7O8Xn81llZWVoa2vD7t27DdiR/PtFIH3YsW93ugc6q9cxU4EZFyyjrHnfeQD0V9FMhwQzMjKwe/duAMBll12GG264AaNGjbLolSktLTWeMS1n9VpqmcJ9xv20c+fOPoqyU8bqTBHew4mFYXpkT08PqqurjQGm04g5FsuyUFJSApfLhcLCQhQWFqK4uBgFBQWIRqPIzs42HfHC4TBGjBiBYDBoDrecnByLcjg3N1doHAxkMWuXMcmyLFvHwYFIK+0EtTot/QMRa1HE43GzTgBw33334YorrjAeuoEoGAyipaUFRUVF8Hq9+POf/4yuri4zj/y5p6fHAA0zMjIwYcIEq6KiwpaqR97i2pH3aCA2NTVJVlYWZYn4fD4rkUigqakJiUQiFRLmTQa6Ojo6MGrUKFsnKCD9Xt6nnHKKiPRWCNPdYriZtm/fLuy/zqYLluqrrH/v9XrNz0VFRcKiIMlkkgLO1kOY1Z4uu+wy07PWtb96k2t/NaPMzEw599xzhTFxfo/vz7moqakxVdj4fqzs4/P5BIDMnz9f+Hmi4js7O42w13Hsu+++W4BUFbnrrrtOfv3rX8vjjz8uTz/9tHz44Yfy1ltvSXV1tXzyySfS0tJi5uuzvPa7H83ccu7T7ed72223maYcBItp3hERVFZWisvlkrPPPttU0ON6iQh2795t4y+36jE+2BUMBuXqq68WHnC0QkR6GwElEgns3LlTMjMzDT/SVfbhhx/KaaedZtbQ2SiBY/L5fPK9733PVJtj0yG+Dy8KSoJXRMQU4iB/cV74u+uuu65PNT89F/fee69p+kFe102B4vE4nnrqKfH7/fKjH/3IVAPjdz7Pl/YS8f/76x/0yw+u/Y1+XP009/k0F++peZOVBMvLy2XZsmVm3+s17ujowLhx4/p0VUt3XCeccIJoeUTvGgtXkd9GjhxpZPJAjahY5fGss84y+4X8QxnN+W5paUFhYaEcc8wxhvecvM7QGA9TvQc4rhtuuMGMgXLT1U8VuYGu7du3CxVaXdGUqaLc46zad/TRR/e5d3+yjF0EuQ5a9gGp5kwPPvigUI6wu2dXV5c5I7Qsb25uxtChQwesaunz+WTfvn2mOyTXlHgrvZ76HXl+cF5ZmTCZTJqCWJdffrkAqY54gwahMjIyUFJSYsBPpHRdetXV1WhqahIngEWk15ovKiqymK9M9wTjOZ2dncjKykJXVxfy8/MNOjASiWDhwoW23GcikbV7iLnYa9euNSkNTpd+IpHA7t27TUjA+f58V5/P18ebQI9GPB5HdnY2nnrqKSxbtkz094kboDbW0dGBrq4uXHLJJdZFF12EHTt2YMqUKbjiiiuss846y5o3b541dOhQHHHEEdaQIUOs3NxcKxQK2Qq7cME/LeXm5oJZBiJi/k0HtAekUNhOwA8PE65DMBjE0KFDUVdXZzRgWisiKQt5xIgR5h7arTcYMc6pic+l1h+Pxw0SldZUU1OTPPjgg3LSSSeZ0Af5DYBBQtMKZJoIf894O4VrZ2en6RzmtLQjkYgBHAWDQVOdrrW1VV544QV5+eWXDR/5/X6zoWlVMl6si3rosJbb7TZ18z/88EObxv95JyeGYn+s1ho+fLhJN9LEg0S7nnUtB+210cRQnbNRjNOCJW+yadSZZ56J5557DvPnz7eam5ulsbFRuMZAav+XlpYaa/5g16S6utrwnj5MOWbGr8vLyw3f6Wdoy5/zs2fPHhMq1POhkeyBQABjxozBli1bTC0E8rNGWg9GlOt6LP0B/Aaiqqoqk1LK8WsgGK1/gvEaGxsBoN/MGa6vfrZzfb1eL1yuVAOqP/zhD0aJyMrKsng+BINBsybaW6tDsnwG+UADh/lsXVVRyxF67ujp83g8JoWPIULGzysrK+WRRx7BkCFDcMIJJ2DQA93tdqOiosI2kYDdvXkg2rZtG3bv3m3c1jx4eT8m8BMNCvSC8rgwLOxfX19vFmDs2LG4+OKLTYcnHYOia4wH3q5du2TdunXm/k5G2m9Foq6uTvRhAPSGE/a7k6wZM2aYCddj9fl8aGxsRENDAxYuXIhPPvlE/H6/AeMFg0GjEbPgjMvlwoIFC1BcXIwbb7wRK1euFIWutRKJhK1ykJ4/5sd+WsrPz7d01zNSugf6qlWrbPFFbnrtWo9Go9b48eOxfft2UEul0EkmkwiHw1ZFRQUSiYQtnS8dl5mkvAy2ioK6xC3TRLxeL0pLS7FhwwasXr1afvazn+GSSy4xTSi0wqQxFBoUSfwBiz+0tbWZdfB6vfD7/aYcKGuB0825/wC3zWtmZqb105/+FOvXrwcAk7ay/28mTsZ85NbWVtGHFQCTPjV06FBkZGSYYj/Nzc3yRXC3a76nzIhEIhg/frwNMDoQOdte0gLi91iWGoARqE7rF4AN1Aik+OWuu+7Ck08+aY0aNcoSEUSjUYspbAAMf0ybNs3G73yfdEB927dvp3fJjJ/yTSucRxxxhJFLGu/En3XcnfvQOXdUOrl3p0+fjpqaGqMw62p26Som06dP7wOk49jSodWrV9tSNgly1FgnnVPf1NRkcAzO99YhqM7OTltIVisZrDOyYsUKvPrqq6IL53C+Ozo6hAqaSCqkMWfOnD4pegzN7ZdBllYoePizzgErLmovB72B+fn5FpDiqXA4bObyl7/8JVpaWnDOOeegqKjIGpSjCJDgIHQMKB2BG4vFDENw4sk4mqnLyspM7FW3CqWrnnHkjo4OzJgxA88//7zNHac1R63BAsBLL72EWCxm8oP5dw0E/OSTT1BTU2M0KZIG/Hk8HkydOtXGzHyGbtm3d+9e3HTTTSamT6tUp0J4PB7U1NRIeXm59eCDD6K2thY/+clP0NzcLFQQOE9Oq+OzJMuyTGqiEySUzvpu374dLS0tQkHi3LzJZCrdoqKiArt378bu3bv7CDKXy2VarfaXDz8YVVVVgf25ueE4z1QYOzs7MXnyZGRmZmLVqlX44x//iK6uLrC2gE6/ocbMn7neeXl5PGjF6/UiFAqhra0NsVjM1jjF5UpVLGRbSQLXIpEIqqurRUSwadMmOeqoo+Sdd96xxTIB2KpydXV1oaamBq2trcjOzra8Xq9B5u7atUs2bNggb775plRVVaGsrAyffPIJdu3aJX6///OfhK7IiZ2ZMGFCn4JL/JzOCGEetcZ0AL0KQjwe7wNSoxLHzIJAIICOjg7Dm8cddxy7W1n0nBApzbLXQG/lxClTphie0mNNZz93dXVh69atts9rLBH/Peyww8zf+ruv5uO6ujrs3r3bNqeUsfRoATB7kpgDHoLaaBqMRo8ebaxl7V1KR7YA9lomAx3A9JpxL44cORIXXXSRLTOEz9XGh1Z4tFdX1xG59957AQCNjY1CXBIAhMNhW7YOAMyYMcPcQxuEAJCfn28r/cpzsLW11dYOmCmT9BAlEglkZWVZTnns9Xpx6623yrJlyzB37lxcfvnlKfmdzqTSgtOWKZC+Fbd582YzQfrA0GA5Io81CIHujYyMDBMbnzx5Mv7whz+gsLDQcmrbfAa1WFbievrpp81E9oe05MJWVlaaewyEtqYnQWvIOhUPSDHHgw8+iGXLlpnmNOybHAwG0dzcLEAqRzsWi+GYY46xrr/+erzxxhu47bbbDNiuvb3dvJt2F+pD/rOg8ePH2zb3wRym8Xgc1dXVxr3NOeaG5xgnTZoEEcHHH39sNqdG1up55RjSERh0O5LHnPE8rbzNnDkTyWQS7777Lurr6xEKhWxNICiAgRSvM1xDAZ2VlcWNaPHwDoVCCAaDRmlg6gkzHDRoEgBKSkqsO++8U4499lhT5Y7j1iUzeYCICLZt24bLL79c5s+fL8cee6wcdthhcvjhh0tFRQVOO+00zJ49GyeffDLWrFmD3bt3Y9euXfB4PNizZ8/n3ueugZZ6H44bN84WGtJAMG3dsI4DlX/eR3+eB7/f74fP5zMKE0GMXDuv14vFixfjqaeeQklJCQKBAOrq6iQSiRjQGdewvb3deJtGjx5tk2vpHmakTZs2mZ/1mKkIJhIJAyztLxOCe1Mr2Zs3bza8pr1RWg6w7gY9SDod17keA1E0GrUYTtNWebpnx6ZNm4w3WD9Tezo4H83NzcKw19FHH23R+NJ1KhgKdMo4LUtZ+Mvj8eDFF1/Epk2bJBQKWSJizhQqP5RTiUTCgDU5P0BvyGd/WprpiEjPHcMzOh2NHk4akXV1dWJZFlpbW5GZmYmuri7ccsst8qtf/QrBYBALFizA2LFjrc7OTtgEX39XMpnEzp07ZciQIQZgMBAgpb8rIyNDLrjgAmFJTA3moQAUEVRVVUlmZqYBJbhUe8Rhw4YJAPnGN74hVVVVooEEGpShYxTUhtevXy95eXk2wINlWbZ34M8LFiyQZDJp3G48GDT4Yu3atQZQwUu3zCQ4DoAUFBTIa6+9JiKp9Lfm5maIpAAnGuQkIqivr5djjjlG3G63+Y5u49ffunwWoKfu7m68+uqrEgwGxbIs29ykA9xxuVxyzz332MZLLZdKmIjgzTffFK/XK1deeaVw/MzfFRG89dZbNsBhuqA8glmWLFki+plcRw0qWb9+vQCQkpIS844ul0u8Xq/4/X4b//HSAMhf//rXwnmLx+MG1KILb/CipZNIJPDxxx/L008/Ld///vf7gDwJznQ+l8A4PQ96LIFAwNxL819xcbE89dRTQkDmp+WP/2sX5/m1114ze41AuP741VLtPPk5AjAJEuuPrwgM9fv94nK5ZMaMGfLSSy8J113ze1dXl0mX4sU4p0gK9Dly5Ejbnhnouc7L7XbL+eefLywhSnlB/hZJgbPq6+tNi2jnu2tZR5655pprbIcLx6r3C8Gq8+bNE/1ulLtcjwOB4pLJJC666CJxuVzi8Xhsa5SOfMnPz5dt27YZmcH3Z1hEy8iamhrxer1y+OGHi4jgvvvus7Vsdan2svxZy2sNpNTrc+ONN4qIYN++fcL5puLAw7ejowM1NTVSUlJi7qXPmy9/+cumhTTnhf+yYiGNAZ6V8XjctC3mOq1bt06+8pWvmPE99NBDQpCeiGDQ6vOWZWHIkCHWmDFjpLa2to9rSuTARoCIYN26dYjFYsIUKidQAwAKCgqsMWPGCIucJJNJY9m0t7fjkUcewfnnn28BQF1dnbBoBxea46G1Tc182bJl2Lt3r21MTIvjGDgedqwiMELHqzjWoUOHmlgsye/3o6WlBbm5udi3b58BatTV1eFnP/sZJk6cKDk5OVY8HhcAVjgcNqCK9vZ2iUajVl5ennXzzTfLvHnzcOGFF+LZZ5+V8ePHW3TFOME86WrI6dCECRMQjUYH7IJ0IBIRvP3227j00ktt1i1TQfi7kSNHoqCgAO+//z4Ae6okkAq5DBs2zIDWBuMrEoGG7733nnGV0jpyWgFDhw5FXl4e6urqTK0CCkZNrF9AoAqQmu/3338fy5cvl/z8fHR3d5sua9ykFOx0ae7btw9r1qzB1q1bweIQHC9DSyJi+EWHY6hsAb3pe7owB61Guuja29txxBFH4Pbbb8dxxx33hXK3cx8CvV6+8vJyDBkyxITzdKgNQJ85BuyygsR9xMJL0WgUubm5Ji00kUjg+OOPx9lnn41hw4ZZ9CiyEBEPQbqV9+3bJ1lZWVYwGEQsFkMwGER2drZVVlYmu3btMkqslkGD0Zo1a9DW1iY+n88iXzitzGg0ak2YMEF27dplAwk7wzncj0yTJLBQW7z8fG5urjV27FjZtm2bSb3lZ9L14omk6kzcf//9Zu2cKXoHooaGBlRWVmLkyJEGOMr9RquWnhRiW1hu9+yzz8YDDzyAt99+21i7NATJE+Qt8g/3ol6bp556Cpdeeql5f76XLsSTkZGBUChkTZgwQdhUh88IBALIzs7uEzKhDKG3yIkroLt9w4YN8v777+PJJ5/EX/7yFyQSCUyaNAl33XUXjj/+eFtKbFpNhTMzM01zEyfKcLAF2W/h24S7RrIHAgGTHzplyhTT17mrqwsjRozA1VdfjQsuuMBspMbGRmFery4ew03CAzgej0tbWxvuv/9+M5ZAIGCD/et6wkAqVhSPxyUQCJhqYE7GzcrKsoYOHSobN240i9rS0gKPx4N9+/aZuKrLleo1/ac//Qm//e1vcfXVVyMcDlt053BTRKNRq6enBw0NDXLSSSdZixYtkjvuuAP33HMPlixZYmqL8yJ9Vge62+1GXl6eFQqFZO/evbY4X7oKG93d/aFX+bu8vDwrOztbtm3bZgMmkZ/y8/Ot/Px8qampMfdNh5LJVF749u3b+7gMucZut5uKmjVr1ix58cUXAdjbtXKtM/aXZdV/p7BYvnw5HnzwQSMAAoGAsRRIB9oXjJHRqqcbkAoFO7WxnSwFEIuU6BAMAKPwJhIJ/OhHP8LChQstVi7TwKHPO+l9yLUtLCy08vLyhJ0SNb94vV7jyiwoKIDP50M0GjU1HPLy8hCJROD3+zFs2DAEg0FziIfDYUtjbQiwpPAFwJxiIyP8fr+xuCj02asaSCn8w4cPN21agV4+GYzPE4kEqqqqbPgOupA1OC4zM9Og6XlfXdVTzyWQwr4w/kx+ZmiC93a73Zg+fTpeeukl1NbWmtKknAd2sTsQWZZlMoMGUi4ORD09PaAhyRAt5bdlpdoac78yfFZWVgYW1DrmmGNkf3Em466m7HW5XKYKo04N00pNIpHA1q1b8ac//QlXXHGFWS+CJGmYcSwjRozok83Aeh2vv/66WFaq+mRWVpZRJKmIxONxtLS0YM+ePdi+fTtqa2vxzjvvmEqeVLy+973v4Sc/+QmKioos1lhgz5BBD3QCIQ4//HA89thjNs0kXWHR0tKCbdu2IRqNIhgMmhi1Frw9PT2YOXMm1q9fj7lz5+LCCy/EpEmTLGotFJCRSMSkCxH5qeO2QEp7zsnJsa699lrRjeZZ912noDD+vd/FhNraWgSDQZuGTyFJLaiiogIvvfSSTXCTqVi3N5lMGsTyD3/4Q0yfPl2OO+44i+9NTZCKTSQSsRoaGmTRokXWihUr5L777sO0adPk+9//vtXR0cGUDCGKlgv5aYkC4eijjzZxNR6E6aSVuN1urFu3ztRp7+zstPXvZSnc7u5uzJo1C8uXL8fHH38sw4cPtwKBgGFSl8uFadOmmXidjg8eiJgGuHPnTixfvlxY6am6uhodHR3Yu3cvtm3bhvb2dtTX16OxsRF+v9/WvpBrqL1HQG+THSqgHAuFIktcamCMs+hHfn4+6uvrMWHCBDQ3NxsPRDgcNn3TyRPxeNxWmY4ljZmuRotGRMz3J02ahF/+8pc44YQTrJaWFmOxf1b88e8m7jnAzvO1tbWycOFCvP3228jKykJhYSEKCgqQk5ODSCSCcDgMv98PFuIYiNJBW1uWZcso0d9xFjIi6cPfsizMnj0bDz30kFnrg0nNTCRSdc2zsrIQCoWMy5tKwX6QpsUmPclk0hgWltVbfVBjOfbu3YvKykqpqKiwqFzygHa5XKaIyWGHHYY//vGP2LVrF8aNG2fbIwM1mHLGkceMGWMqLxKlzgM6Hdq4caNRttva2uDz+WwtqLkGLDBlWRbC4bDV09ODa665BnfffbeZFxKVAgA2Dx3HxH952D/66KM4+eSTTUiW4N7m5ma0tbWhra3NVK1ksRntEfjHP/6Bk046qc+7OYuo6X3O/9PbU1hYiB//+Me4+OKLjVWu97jb7R48hs744FNPPdWnEEA6MRDsj/E9/PDD0tnZaQ46uhhjsZgp0sG4DX/m5w508TDmZxmn+POf/5zW2IDeAgAej0deeOEF4X10nFrH///nf/7H9r3Brvz8fJk2bZpUVlaaeIie4x07doh+TnV1teTn58u4ceNk06ZNIiIm/s74HA+AwdZvsIsa6+233257p/7iugNdlmXJunXrhEAPCiyuDcf7wAMPiNfrtRXh0MJt8eLFtsIu6cYZebGwhs/nk0AgIEBvAYlgMGj+fjD3ZSx7MNyIx+Oxxbs1zuLb3/62fPDBB3LttdemtXeIZdCf4fM5P36/Xy655BKpra01c6n5oaWlpQ848PN8EWDI/yeTSdTX18tg78gDaKDrf2PclEe6KE26e4t7cvny5aJj5txfROqLCFauXGn4hryon+PcWy+88IJ0d3eb7l6af5hdtHz5cgEgP/7xj/9pXEZdXZ1MnTrVnAUHe34ce+yxhse1TKaM/uSTT0RE8MorrwgAWbRokejPXnrppQJAotGouaeOnQ+2r/kzMQqBQED8fn+f4jXEIR2s/PJ4PBIMBvvghqLRqLnHaaedZjs/9Jmpr7Ty0F0uFyZMmGDy84CDQ0J3d3dj1apVxlIDel3G+xu2WwS00Z1O989gxCo+Pp8PtbW14vV60dDQIFdffXXa49OpeKtWrTIeCL6nLgYApHoya6tsMGpqasKaNWvwm9/8BkDfnuM6xzUej6OoqMj60Y9+hI8++gg333wzkskk2tvbhR4Gfj/dFqwHImqCrN2skZbprnFGRgY2btxoQh/79u2TnTt3ytatW2Xt2rXy3nvvyeOPPy5vvvkmRAT19fUAYFDYIgIApg0oMHD6jZM4B+Fw2Fjd8Xjc/Mw5I/CkuLgYM2fOhG45eSCiW1OPRc8T88CJ2aALkqV/77rrLixatAgVFRVWaWmp6eAVCoUGdFfqDUra785FZ2cnSkpK8Nhjj+Guu+6yCgsLLf2e/KxGJH/eifOva0JYloW8vDyL4CRd3EenjlHODHT9b9HYsWNRWFgIkfTSQUn0Aq5evdpYfJovksmkwVxMmjQJ9HrpeLMzFMZ5XLt2Ldxut8kk0pRIJCQej2P27NkoLCzExo0b4ff7bZ9Lt7BVNBq1mAKneT4d+RKJREzXOZ1myLlJJBIoKCiwEomEaQ3LEAifwRQ2Pd508+BZGyMcDhu5xcJg+kwgEJhpkkx7HIwYtovFYmZumJve3NyM7Oxs3HvvvXj66aetUaNGWfyObvtso3Q0rJ6eVBOS8vLyfjWXA13UWI4++mgRERMv6C+9iOh1p6V3oIufo0YpIjjjjDOMZpvOGPkubrdbTj/9dBERW/1saq/U+JqamiQ7OzstDYyWWiAQkEgkIo888ojRsohq5DNouTOn+pJLLhEAsnz5cjMmvqMuSftpLgqIrVu3SjAYtM1ZOtkMfL/Ro0fLCSecIOXl5RKJRGxIUuzXYGk1f/3rXxetYXIMVVVVkpWVlfaznePoz7KNRCLmuSeffLJs2rRJ/uM//sM29oO5mAXA8WmtOhQKmf/Pnj1bXnrpJdHW4LPPPjtghkRmZmYf1DWf5fV6bXO3a9cuY5k2NTWJtpq4L7hv/9UW6P/GpetL9PT0mNzdf/e40rk47s7OTsyYMcPwdrrWGz93zDHHCO/D7AleDAeKCCZMmGDzsmkZ7JR3Z5xxhsTjcSPfWNZYj3/fvn0ycuRIKS0tFdkvpw6mBDXvt3jx4j577mD2+H/913/Jww8/LL/4xS/k5ptvliuvvFLOOussOf7442Xq1KlSUlIiWVlZ4nK55He/+53Q1U3Zcvrpp9vOBGeJ58EujZbnvvV6vSZr4mA8Ls6LMg+AsfwDgYAcc8wx8v777xvPpy7/O5CFntaikFnmzZtng/4fzKCHDx8ujY2NQkuc/9bX15tUEL1J4/G4Afwc6BIRkzKSSCSM6zhdl4peJMuyZNy4caJrwzvnggwyc+bMtBiS80VGHjFihHz00UfCjcGx877cLMlkEhs2bJD8/Hzx+XzyyiuviIigsbHxX1Kru7W1FWVlZbZDKh0m5UHj5AnODd1cwWBQfD6fBINBWbx4sXC99Lt3dnZiwoQJB7XZmX6k1zsvL8/mdgyHw3LrrbeaedN1/Q/m4gGr5yUUCpl3d7vd4vF45KqrrhK6AVlPQESwefNmKSgoMN9zuti0O1b3MeD9H3jgARERUwSnv3V0ut3/3Qfap720cp/O1dPTY6uP/e8ev0jvofa1r33N8OzBHuhFRUXS1NRk5CcPchF7f4xvfetbg+5bHujl5eWiDSEaW6yLTr49+eSTJRwOS319vejQ48Fcv//97/vIiHTmgAdpKBTq00MjMzPT7Huv12tq6//+978X8j/n5fnnn7cdmOnOfyAQsKWP6h4m2vjRc869m07qLc8Fl8slOTk5Zn1+8IMfCFPW9N7We7qurk6c85zWYnBj3HTTTUYbSTdPmC/q8/lk1apVojed88DUmqfI4NY5cybJeLfddpsAkMLCQpuwTWfDcKzRaFS2bNkifG8CUPg8Puuiiy5K+yDQsRsAcv7559uabXBTJpNJk6PO2Ojrr78uwWBQZs+eLa2trbacUb0ZP83Fd6MWy0P6YLVOn89nmF/zR35+vgCpg/bFF18UEbFpmDof/ZxzzhHg4C30gTZLRUWFvPrqq+ZZ8XgcX//618140+FfHuSZmZn9CoKCggJxu91SUlIiTz31lOEdZ4Of2tpaGTt2rO3ZvK/H4+n3nUtKSuT000+XDz74QHp6emwbmnzQ3t5uqpX9XznEPqtLH+Z0c2q+oQv2/6o3giECEcGiRYsMDx0MPqU/+ckQD9efWKdf/vKXfQ4bp5zj3oxEIvLxxx8bA0bzFd3bPT09uOyyyyQSichrr70mep4Pxkvy3nvvSTAYtOXFpzsH3CtU3p2NwVhnZPjw4QafoxunkE+mTJliy4VP14ML9CoikUikTy0WjuvTyCquybhx4+T5558378DWs3ou29raBtzng6LciSbv6urCtGnTDEqTsfDBiJ/v7OzExo0bMX36dOO6zszMRF1dneTm5lo9PT0mztHZ2YlwOJxWnJrxhp///Ody7733Ijc3F9OmTcNf/vIXgzgf7PtMgbAsC83NzaZvt44jk8mJIJ45c6YtJe5ApGMt4XAYv/vd73D88cfL1772NVPbl4jwcDiM7u5uFBYWWvX19fLMM88gFovhjTfewCuvvCInn3yyRSS1xjR8GmLM9bDDDsNzzz1n3jmdtBrd6EYjtLkuXV1dqK+vx5VXXolbb73VYCQCgYBJudCxtKlTp+KPf/zjoM/VzyEOIBwOw+v1Yu/evSgpKcEJJ5yApUuXIhqNWhSAwWDQ8FQ6PMw5YBxSo9kDgQDC4TBqampw4YUXYvHixSgoKLCIzGZKik7d+9KXviTMEMnNzcWQIUMQCoVMShXTqvLy8hAOh7F3716ceOKJJhUqHA7bDjrmT2vq6uoyqUyfd3LGWXVtCMoM8ormGR5Qg8VK/9VxdD3eiooKU7MCQFrybSD5KdKbWqXTzpiDfaA9zP0Si8WwZcsWxONxaW5uRnNzM1paWlBfX89iNYjH43jzzTfR0tKCN998E4cffrhB8KcTI+beGTlyJIqKivDxxx+bVON0iCmEQK8c1Qhwt9uNvXv34jvf+Q4WL15s0gY1+juRSJXSvuCCC/Af//EfJpaezhgo3/gdrp3ObuB9nGWvNRZrIGJlwo6ODnz3u9/FPffcY/n9ftTU1MiwYcMsNmjZH2aSgoICKxgMmqpxfWgwzUq3p3vnnXdsbo90tBL9mTvuuENExICWnFoHf6YVzNang7ncFyxYYDScu+++W1555RVjFQ526bas1LpYEUzHrLX109XVhddff/2gNDBqyHxGOByW9evXi4jd0qZV9+abb8pRRx0lwWBQQqGQ3HvvvULr7LOMHxIfISJ4+OGHzdjSXV9e1KK19pyRkSHFxcUmc4Bj19kIVAb476OPPiper/egvAPBYNBo0KFQSIqKiozbjUWGyMednZ048cQTbWuf7vpprZzPWbBggaxZs0Zo2Wjvkp5n8k5dXZ0cKGxCNLf+/YFc7LqlpMZ9fFEuhr7SRbM7P0eszkDXv3r82pJatWqVTS6l4+XUljVDVboCI1totrW1obm5GStXrjTf03gSZ6hIx31zcnL6oO+1a5x7+sYbbxSuRbp8pj83d+5c273TcXvz2fQa6u8EAgEJh8Ny//33C70U3Hes6qbHsHfvXhkxYoSpWpfOvtdz5vP5bBUmndkvDJUdzLkAQMaOHWuwVSJ9s5cGCnE4s6VE0rTQgZSmNGbMGGRnZ6Opqcm4fgcjnc/HKmHt7e0SCoUsIjGBFDKRzEKri40tWD8X6K3H3tnZicrKSrnuuuuwYsUK+P1+3HfffTj//POtmpoaaWxsNLm6ByJdWID3X79+PZqamqSzsxNNTU2ora2FiKCxsRE7d+6EiGD16tXmHs7cQSexQAnQ20YvHo/j0ksvxYsvvohwOIy2tjYEg0EEAgHcfvvtsnjxYnR3d2Pq1KlYunQp5syZY9EC9vl8NkT1pyGXy2WK17B2c2trq61SHr0XOl9SE8fD+ePvTjzxRDzyyCPGo8Bn8blMYWRFLhHB5MmTTY11bX3rfG1eAMxh1tnZiYyMDHz961/HokWLMGLECKu9vR2BQMDUwW9vbzcFOPLz85GZmWlcWnRZamKxiFAohPz8fAwfPhxjxozB9OnTMWPGDJSVlVm6TWN/+ckkrhW7JmnSc8piE/y9a38/g4GI+6c/S/2LQHo+DkQDIab/3Uh/8ikAjBkzxopGo7J3716jbAxGevxPPfUUYrGYiAi2bt2Knp4e7NmzB01NTWhtbUV1dbX5jpajQF9rVMtuGgoADEKe3kICEE866STMmzfP1ho0HZS6ro541FFH4R//+IeRtZSFB5oLjqu9vd1UnwRS6PdRo0bh2WefRXZ2tsW2w1Ts6AnkGJLJVFfHSy65RG6++WbjPdSFePgsWv4ZGRmIx+MmD1xETO0Tv9+PWCzWJ1uCdQf0s7VHgc8YNmwYSkpKcNNNN2HSpEkoKyuzOK/0fLDGijMrisRmL30m7EAXLRuCe4488siDAsRRE3S73TJmzBjRcTD+TIAYLSla8BoB3t7ebkAAO3fulIsuusigA6dMmSKPPvqo0XCSySSOO+64tMdI1CkBCj6fT2bNmiXRaNQG+nJq1FpDsyzLVotcX/p7zjjp0qVLDeJ79erVMm/ePMnJyZGMjAy55JJLDLhKr4XIZxc/1/fct2+flJWVpaW9UkPV88P5C4fD8tBDDwm1zf5inLS8aJFyrZuammxxZiLKnRY7eYqegVGjRsny5ctNjK+5udm0OHXOWUNDg1RVVUl9fb1s2rRJ1q9fL2vXrpX3339f3nvvPXnvvfdk1apVsmbNGqmurpa9e/cK44mfdf7yv9uCPHT9ay/NK52dnTjppJOMlXmwdRb0HuP+cP6dcqg/WZPOnqa84ncjkYjccccdQi/eQHHdga5YLGYszmXLlonX600bMOZ8T52NtHDhQkkmk2htbbXNcX/YLG3JfvTRR5KXl2eztA+EafD5fGY+5s6dK8cff7xEo1EJh8Pi8/kG/K6l6sH7fD4ZNWqUzJs3T6655hp54IEHjHf2k08+sdVSoJwR6evlS+ca1ELXXWr8fj+mTZuG9957r09FrIFIV97ZsmUL9uzZIyUlJaZsIqm/mB83hGVZqKurk48//hj33nuvPP/88/B6vWhra8NVV12FG264ASUlJRYtfI/Hg2g0OujYAHvdbFZRisfjWLdundEGNW4gY39/bbpB9FjpceDndflAErED1OKuu+46VFRUIBKJyHe+8x189NFHGDZsGJYtW4bTTz/dYiU7nafImPdnQboXdHZ2tlVaWipsl2jtL1MI9Fbz47uQCTlnPp8PsVgMZ555JpYuXYqioiKroaFBtKbsJHo2RHprQ3d3d6O4uBiVlZXGuvb5fAiHw8jKykJOTg5ycnKQlZWFQCCAvLw8lJaWYuLEiSgpKbF4n0gkMuA7Z2dnW9nZ2QBg2qeK9Lr+OTbmADtbb+oiJ5/WKv53W5CH6F9Lzjrh06ZNw8qVK9OuxEhrkeEEXW+AFQyBXouZISYgvRgxkLL0aFQBqT3o9/tRXl6O//7v/8aECROMF2+/pQsgvVoVtCK7u7sxduxYMxcsg0v5wwNJH24AkJubi4aGBlPlbtKkSVi2bBmOPPJIq7293YYjErF33iRxjyaTSYwdO9aaPXu2rFy5Ei6XC7q2h8fjgdfrBTtkUvbm5+djxowZuPbaazFy5EirqqpK2tra0NnZiYaGBltYCIApm+tyuTBixAh4vV5Eo1FTtZAYGiDVw4Tj5Pzz3dPNldeUVnMWTRUVFbYBpUtMoN+yZQsKCwtt7ttYLCaNjY1oampCU1MTGhoasG/fPrS0tGDDhg146623sHfvXpv7fOjQoXj00Ucxb948iwed2+1Gc3OzeDwe66ijjsIzzzxjO3T7I70peFjrxhfcKOxlTs8BSZeO5aEOwAbccc6BBmNlZmbi9NNPRygUQl1dHQ4//HA8/vjjKC0ttTo6OtDW1iahUMhybqJ/ZrEHIl12cNy4cXj55ZdNEYmBgIVsSEBABwAsXboUF198sUUhNNhhzgOT4CYgBRz79a9/LTU1NYhEIigqKoLH44HP57NYLEVveLriSVQAdeEXaruhUMiA1GKxmLCWPsfT35zqMfJ5rA9/iA5ROqRlUEVFxUF9V7ulScFgEJZlmb2ZlZWFhoYG4wY+GLIsy8gz7q/u7m6cc845+M1vfmMxHNba2mpCggBMv4B0KZlMmsI3zc3NRpnRxV64j7kXLcsy9dnb29uxcOFCLFiwwEomk8L2xvr+GtxMSiQStvKqLpcLixYtwuzZs+H3+1FSUoLMzEz4fD4EAgFzBYNBeDweI8NaW1uN8jB06FArHUAgyemGB3p5QlviLNv8qSgdM16nXvz1r3+1uSswiLuEqWN0l0yfPl3OPPNMmTt3rsyaNUsKCgokGo2Kz+cTj8cjXq+3T2qBztXLyMiwlcFLJBKmsb0e8xNPPJE2QEG7rvr7TkZGhhm/x+ORc889V775zW+aMbEUID9PNw6/29/9fD6fccswhPGLX/xCOP6B3C3aRa7dyZ/m0oCxpUuX2sIEHCNd33Rzh8Nhyc7OlrKyMjnqqKPkjTfeEJHekqM6ZEIeopUxkCtZl7elm46pegONPZFIoKWlxbb+GgTH9+JcOedVg6kOxsXNnuWfxfwfur7YlwY1vfXWW/+Uq93p1tVyiqFHDdpK193OFFN+fsSIEfLss8+KSMpVrfck3+Vg8tAJbOZ+njlzppGRbFdMuaJDa7qA06RJk2TlypWiwdTcxzybBgrraYCZBijyHfoDUupr7969pmaILmQmIqbNNo00fek6CMT4MMQ8WMiOad3/DK+lZWYkk0ljkRQVFaGgoAB79uxJK+2CWmRXVxd8Ph9Wr16Njz76yGblHogYZ83MzEQkEsGNN96IBQsWWCIpDScWixn3qm7xl5eXh0AgYNIMDkQETdF1QjAULbGmpib09PRg7NixmD9/Pq6//npr1apV8uqrr6KmpsY06QBSljmBEzzENJgDSLmA9CKMHj0a999/PyZMmGABMF4G3dGHVq9OozoQWOpgiGMXEcyaNQvnnHMOcnJy0NXVhbKyMvh8PkQiEePuzs3NRXZ2NjsvWQQvAqnUMd1Ew8kfIvbWiwR+ALC5z+iK07/rz7VlpRoxmHtpIJzzXgDQ1tYm0WjUojY/mJXhLPlKOmSlH6J0iTyUTCZRVFSEIUOGgF0FByPtkWIKky47OmvWLEyfPh2/+93v+rQ/ppfwQNTd3Y1QKIRYLIazzjoLy5YtQ25urrVr1y4pKioyZmVPTw86OzuNazpd0hY9AJSUlGDDhg0mhZQNuxhOy87ONv+yC97xxx+PQCBgvHgdHR0mnYzgNc4ViXPu9XpNG1se3LFYDKFQCIlEb2MXKgA8cHk/3TKV89De3m68uU6PoH4+79mfR5XnmrOBEg/0fzoUl86pr1NsGhsbZc6cOQNas85LW6MsLACHhkjtrL+LoKs5c+bIu+++K85xdXV12bQwbbWywEw6Fyt9aTCDvr7yla/YCpTs3r1bnnjiCcnOzrZ9ThcI0Va61+sVn89ns+R9Pp9cffXVoi1ZbWnq3+srHo8f0Go9mCuZTNrutR85a9KqWBlKV/frL42oq6vLVrmourpaiBw/kAWsGV+vYWtrax+vy0DpMu3t7WAVLf6OaVzOFBCtNTtT5waz0gng/GfAKoeu//9etCopr44++ui0rfT+wKDY7/mcM2eOvP7661JbWytz5szpt3nWYBcAKS4ulvvvv19EevcNx0754ARu9Zcy1d/lLMH64YcfymuvvSbr1q2TPXv2SFNTk8RiMaOk9CdbOH/JZDItryTllPP7vPQ9dFGrA63fpy3YxEJTztRSjfKndf9pwLCDfoDuUy52MpnEueeee1A5d/0hoely6Y8B9f8zMjLkpz/9qei8S10tjZezznlPTw9OO+20tA7yAykjHo9H7rjjDiEDa5R+IpHAV77yFRk2bJh5J+2+93g8fdz5fN60adPk0UcfNbWUk8mkcW8RoMdncOH/VS5ebfnqA4//588aGMcDvj9GTwcFznvRtcRqVc7v9sfgegz6UGY9/P7cVc77aP7p7700L/2z7q9D16FLpFdJJ6+ed955B1UDwe/32+RUNBqVq6++WnRN+5NPPtnIGO16H+xAP/XUU+Wtt94SEbHJVV0fXmTg6p4Hc/X3XefeYhMlutB1Ns9A6HWnIq7d73wHnZeuf99f3QKnHOhPnrE0uf5ef0YBe4D0994dHR22v/VnJB3sHKf1IT05iUQCS5YsMVoimaw/+L6Ohzg1UqZXDJQ2kJOTI+Xl5bJ+/XpbTVtafc7x6QI4nIyFCxfarH8+i5eOM7EBBscZCoWkrKxMNm3aJI2Njeb5TmWiurpaJk2aJABMLWG3221+1rF3vuMFF1wgO3bskHTm/tB16Dp0fX4vHrhU3EUEd999tzmoteHCEqLsCUBjIDs728iqqVOnypNPPila8W5pacGPf/xj8xl6CaPRqOTm5srw4cNl7NixMn36dDn22GPlG9/4hixYsECWLFkih1Ikv1hXWkFAJ/qwpKQEgL0xfH+xdBEx/4qILbFeRAzCnPGgaDSK5uZmuN1unH/++bjlllsgIgiHw7YiAYxXMP6qY0WMVbjdbjNOjo+f0RQKhcAUBK/Xi2QyVd71sssuw+LFi63W1lbRzyeyG0jFQYYOHWrdcccdcskll2Dv3r3m9y0tLbAsC11dXRg2bBj27NmD3NxcLFmyBOedd54lIrb48SE6RIfoi0eMkbJgiIhg/PjxAGBkDYmhKSc1NjYiKysLl156Ka644gowts002nA4jFNOOQUFBQWIRqPIyclBRkYGsrKykJubi1AohGg0ajA5lINaHh+iLwYd1IFOGj9+vMkL9Pl8Nu1OgzichyhTfqz9fWzp5uSh2tzcjEmTJuGGG27A1772NcsJ+mK1MZK+N38mUMmyLEyaNMmAJ/qrMgakQHvDhw9HOBw2qRXf/OY3cdxxxyEjIwPZ2dnm5Z15lyIpUMaXv/xl64QTTpAXXnjBljOZmZkJl8uFmpoanHbaafjP//xPjB071uJ4DtEhOkRfbGK3OMoyEUFxcTHy8/MRi8VQXFyMQCCA7OxsWy1/gk5HjhzJ3u8YNWoUotGoxWJN7LsNANOnT7cmT55scq5ZP4FpW5p0xgmBZofk0ReDrME0NG1FdnV1wePxoLW1FVOnTjUFSJyky3R6PB4DJrI9eD9q2+12o6OjA4WFhbjmmmswf/585OXlWSIpdxVRkoxtMGEfsGuaHBs/63K5UFdXJ1deeSV8Ph+i0Sjy8vJQUFCA/Px8ZGVlwe/3o6ioCOFwGJmZmVZPT48EAgGL99GbQVvUZH5uCrWpZM2aNQBSuaFNTU3Iy8vDwoULceGFF1rBYNB4AoBUycUDFUA5RIfoEH0xSIHDJCcnx9q8ebOUlZVZImLzOmqZBqQMDmZ60AjSOdAiYuSUlkvMx2YZWOJgdNGTQ/TFo0EPdF0zvLOz0xQfOOmkk+Rvf/ubSQfQaGZnIQQglTrkcrkMU5KxJkyYgG9/+9s477zzUFhYaHV0dPSpUEbm0+lKJP5dKx68v05LAHqLmTgpFovB7XabjdLY2CgZGRlWJBIx99DpYv1RPB7HO++8I6eeeqoB5c2ZMwcLFy7ESSedZAGpdAtq0LW1tTJkyJBDavEhOkRfYNIKPNAbfqRcZeEqyhfKTqZC6ZCkds87uwdSJg70vP5k36GQ3xePDtpCB1KH8zXXXCP33nuvLY6uDz1qnaFQyPzs9/sxdOhQTJ06FUceeSTGjx+PiooKixqj/i4A22GqSVcE6u+Q5d85Jv7OCfwA7C0AnS71lpYWM35N+pnNzc3i8/lM5aB77rlHLr/8clx55ZW4/fbbrUgkYhuvztF2br5DdIgO0ReLtJePspT73mmN90dO+UbsEQ0rTWz6xJaczBsnaS8ADauBShenM7ZD9H+PBo2hZ2RkGG2SJfQAoLy8HAAwefJkRCIR0885NzfX9HOORCIIBoPIycnB0KFDkZ2dbbFPtHYPaXAb0GuJH6hwh2Y47UXQCgi9Bfycdm1p4kFOq76rqwt+vz8tdzhLh+7vWCTf/e53rQkTJsgxxxxjATCbis9l0ZmMjIwvRL/qQ3SIDtHApGUYi2tRiSfOSBsnzn4CWlZSzmlPYigUsvgZZ7Epp7HA+7pcrgMWhzkElPv80v8HqEsG1LXKyTMAAAAASUVORK5CYII=""" app = Flask(__name__) # ── Hall extraction ────────────────────────────────────────────────── def extract_halle(text: str) -> str | None: """Extract SPIEL Essen hall+stand from text. Returns e.g. 'H3 B123' or None.""" import re if not text: return None # Pattern: H[1-7] optionally followed by stand letter+number # Matches: "H3", "Halle 3", "H 3", "H3 B123", "Halle 6 Stand A42" patterns = [ # Halle with stand: H3 B123, Halle 6 A42, H 4 C7 r'(?:H|Halle)\s*([1-7])\s*[,\-]?\s*(?:Stand\s*)?([A-Z]\d{1,4})', # Just hall: H3, Halle 6, H 7 r'(?:H|Halle)\s*([1-7])\b', # Stand alone: B123 (only if preceded by "Stand" or after hall context) r'Stand\s*([A-Z]\d{1,4})', ] for pat in patterns: m = re.search(pat, text, re.IGNORECASE) if m: groups = m.groups() if len(groups) == 2 and groups[1]: return f"H{groups[0]} {groups[1]}" elif groups[0]: return f"H{groups[0]}" return None # ── Game title extraction from cover images ────────────────────────── def extract_game_title_from_image(image_path: str) -> str | None: """Use Gemini Flash vision to read the board game title from a cover image.""" import base64 import openai if not os.path.exists(image_path): return None try: with open(image_path, "rb") as f: img_b64 = base64.b64encode(f.read()).decode() except Exception: return None # Read API config config_path = os.path.expanduser("~/.hermes/config.yaml") try: with open(config_path) as f: import yaml cfg = yaml.safe_load(f) api_key = cfg["providers"]["datenhimmel"]["api_key"] base_url = cfg["providers"]["datenhimmel"]["base_url"] except Exception: return None client = openai.OpenAI(base_url=base_url, api_key=api_key) try: r = client.chat.completions.create( model="gemini-3-flash-preview:cloud", messages=[{ "role": "user", "content": [ {"type": "text", "text": ( "This is a board game cover. What is the EXACT game title?\n" "Reply with ONLY the title, nothing else. " "If it's a known game, use the official German title if it exists.\n" "Examples: 'Cascadia', 'Flügelschlag', 'Die Siedler von Catan', 'Azul'" )}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}}, ] }], max_tokens=50, temperature=0.1, ) title = r.choices[0].message.content.strip() # Clean up: remove quotes, extra text, limit length title = title.strip('"').strip("'").strip() if len(title) > 120: title = title[:120] return title if title else None except Exception as e: print(f"[vision error] {e}", file=sys.stderr) return None # ── Database helpers ──────────────────────────────────────────────── def get_db() -> sqlite3.Connection: if "db" not in g: g.db = sqlite3.connect(DB_PATH) g.db.row_factory = sqlite3.Row g.db.execute("PRAGMA journal_mode=WAL") return g.db @app.teardown_appcontext def close_db(_exception=None): db = g.pop("db", None) if db: db.close() # ── WhatsApp API helpers ──────────────────────────────────────────── def _gdpr_delete_sender(db: sqlite3.Connection, sender_id: str): """GDPR: Delete all submissions from a sender, leave a placeholder.""" # Delete media files (supports ||-separated paths) rows = db.execute( "SELECT media_path FROM submissions WHERE sender_id = ? AND channel = 'whatsapp'", (sender_id,), ).fetchall() for r in rows: if r["media_path"]: for p in r["media_path"].split("||"): if os.path.exists(p): os.remove(p) # Delete all submissions from this sender db.execute( "DELETE FROM submissions WHERE sender_id = ? AND channel = 'whatsapp'", (sender_id,), ) # Insert GDPR placeholder db.execute( """INSERT INTO submissions (channel, sender_id, type, content, status, category) VALUES ('whatsapp', ?, 'text', ?, 'gdpr_deleted', 'dsgvo')""", (sender_id, "Nutzer hat die Löschung verlangt"), ) def send_whatsapp_message(to: str, text: str) -> dict: """Send a text message back via WhatsApp Cloud API.""" url = f"https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/messages" headers = { "Authorization": f"Bearer {META_TOKEN}", "Content-Type": "application/json", } payload = { "messaging_product": "whatsapp", "to": to, "type": "text", "text": {"body": text}, } r = requests.post(url, headers=headers, json=payload, timeout=15) return r.json() def generate_thorsten_tts(text: str) -> str | None: """Generate German TTS using Piper Thorsten voice. Returns path to OGG file or None.""" import subprocess import tempfile PIPER = "/home/hermes/.local/bin/piper" MODEL = "/home/hermes/.hermes/cache/piper-voices/de_DE-thorsten-medium.onnx" FFMPEG = "/home/hermes/.local/bin/ffmpeg" LD_PATH = "/home/hermes/.local/lib" ESPEAK_PATH = "/home/hermes/.local/share/espeak-ng-data" ogg_path = str(MEDIA_DIR / f"onboarding_{datetime.now().strftime('%Y%m%d%H%M%S')}.ogg") try: p1 = subprocess.Popen( [PIPER, "--model", MODEL, "--output-raw", "-"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={"LD_LIBRARY_PATH": LD_PATH, "ESPEAK_DATA_PATH": ESPEAK_PATH, "PATH": os.environ.get("PATH", "")}, ) p2 = subprocess.Popen( [FFMPEG, "-f", "s16le", "-ar", "22050", "-ac", "1", "-i", "-", "-c:a", "libopus", "-b:a", "16k", "-f", "ogg", ogg_path, "-y"], stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) p1.stdin.write(text.encode("utf-8")) p1.stdin.close() p1.stdout.close() p2.communicate(timeout=30) p1.wait(timeout=10) if os.path.exists(ogg_path) and os.path.getsize(ogg_path) > 100: return ogg_path except Exception as e: print(f"[tts error] {e}", file=sys.stderr) return None def upload_whatsapp_media(file_path: str, mime_type: str = "audio/ogg") -> str | None: """Upload media to WhatsApp Cloud API. Returns media_id or None.""" url = f"https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/media" headers = {"Authorization": f"Bearer {META_TOKEN}"} try: with open(file_path, "rb") as f: files = { "file": (os.path.basename(file_path), f, mime_type), } data = {"messaging_product": "whatsapp"} r = requests.post(url, headers=headers, files=files, data=data, timeout=20) result = r.json() return result.get("id") except Exception as e: print(f"[upload error] {e}", file=sys.stderr) return None def send_whatsapp_audio(to: str, media_id: str) -> dict: """Send an audio voice message via WhatsApp Cloud API.""" url = f"https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/messages" headers = { "Authorization": f"Bearer {META_TOKEN}", "Content-Type": "application/json", } payload = { "messaging_product": "whatsapp", "to": to, "type": "audio", "audio": {"id": media_id}, } r = requests.post(url, headers=headers, json=payload, timeout=15) return r.json() def download_media(media_id: str) -> tuple[str | None, str | None]: """Download media from WhatsApp. Returns (local_path, mime_type) or (None, None).""" url = f"https://graph.facebook.com/v25.0/{media_id}" headers = {"Authorization": f"Bearer {META_TOKEN}"} r = requests.get(url, headers=headers, timeout=10) if r.status_code != 200: print(f"[download_media] Meta API returned {r.status_code}: {r.text[:200]}", file=sys.stderr) return None, None data = r.json() media_url = data.get("url") mime_type = data.get("mime_type", "unknown") if not media_url: print(f"[download_media] No URL in Meta response for media {media_id}", file=sys.stderr) return None, None r2 = requests.get(media_url, headers=headers, timeout=30) if r2.status_code != 200: print(f"[download_media] Media download returned {r2.status_code} for {media_id}", file=sys.stderr) return None, None ext = mime_type.split("/")[-1] if "/" in mime_type else "bin" filename = f"{media_id}.{ext}" local_path = MEDIA_DIR / filename local_path.write_bytes(r2.content) return str(local_path), mime_type def auto_process_bg(db_path: str, submission_id: int): """Background: after transcription, run LLM to summarize + extract halle/stand.""" import yaml as _yaml try: conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row row = conn.execute("SELECT content, halle FROM submissions WHERE id=?", (submission_id,)).fetchone() if not row or not row["content"]: conn.close() return text = (row["content"] or "").strip() # Skip if text is too short or placeholder if len(text) < 20: conn.close() return # Read API key config_path = os.path.expanduser("~/.hermes/config.yaml") api_key = "" try: with open(config_path) as f: cfg = _yaml.safe_load(f) api_key = cfg.get("providers", {}).get("datenhimmel", {}).get("api_key", "") except Exception: pass if not api_key: conn.close() return prompt = ( "Du bist Redaktionsassistent für brettspiel-news.de. Analysiere folgende Community-Nachricht " "und antworte NUR mit diesem JSON-Format, kein weiterer Text:\n" '{"summary": "Kurze Zusammenfassung in 1-2 Sätzen, max 250 Zeichen, journalistisch, Präsens", ' '"halle": "Halle und Stand im Format z.B. H3 B123, oder null wenn nichts erkannt", ' '"spiel_titel": "Name des Brettspiels falls genannt, sonst null"}\n\n' f"Nachricht:\n{text[:2000]}" ) import requests as _req r = _req.post( "https://ui.datenhimmel.work/api/chat/completions", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json={"model": "deepseek-v4-flash:cloud", "messages": [{"role": "user", "content": prompt}], "max_tokens": 300, "temperature": 0.2}, timeout=20, ) if r.status_code != 200: conn.close() return import json as _json data = r.json() result_text = data["choices"][0]["message"]["content"].strip() # Parse JSON from response (may have markdown wrapping) if "```" in result_text: result_text = result_text.split("```")[1] if result_text.startswith("json"): result_text = result_text[4:] result_text = result_text.strip() parsed = _json.loads(result_text) summary = parsed.get("summary", "").strip() llm_halle = parsed.get("halle", "").strip() if parsed.get("halle") and parsed["halle"] != "null" else None spiel_titel = parsed.get("spiel_titel", "").strip() if parsed.get("spiel_titel") and parsed["spiel_titel"] != "null" else None # Build update fields existing = conn.execute("SELECT halle, spiel_titel, summary FROM submissions WHERE id=?", (submission_id,)).fetchone() updates = [] params = [] if summary: updates.append("summary=?") params.append(summary[:500]) if llm_halle and not existing["halle"]: updates.append("halle=?") params.append(llm_halle) if spiel_titel and not existing["spiel_titel"]: updates.append("spiel_titel=?") params.append(spiel_titel) if updates: params.append(submission_id) conn.execute(f"UPDATE submissions SET {', '.join(updates)} WHERE id=?", params) conn.commit() conn.close() except Exception as e: print(f"[auto_process error] {e}", file=sys.stderr) def transcribe_audio(audio_path: str) -> str | None: """Transcribe an audio file using faster-whisper (local). Returns transcription text or None.""" import subprocess import json WHISPER_PYTHON = "/home/hermes/.hermes/venvs/speech/bin/python" script = ( "from faster_whisper import WhisperModel\n" "import sys, json\n" "model = WhisperModel('base', device='cpu', compute_type='int8')\n" "segments, info = model.transcribe(sys.argv[1])\n" "text = ' '.join(seg.text.strip() for seg in segments)\n" "print(json.dumps({'text': text, 'lang': info.language}))\n" ) try: result = subprocess.run( [WHISPER_PYTHON, "-c", script, audio_path], capture_output=True, text=True, timeout=120, ) if result.returncode == 0 and result.stdout.strip(): data = json.loads(result.stdout.strip()) return data.get("text", "") else: print(f"[transcribe error] {result.stderr[:200]}", file=sys.stderr) return None except Exception as e: print(f"[transcribe exception] {e}", file=sys.stderr) return None def _transcribe_bg(db_path: str, submission_id: int, audio_path: str): """Background thread: transcribe audio and update the DB record.""" text = transcribe_audio(audio_path) if text: try: conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row row = conn.execute( "SELECT content FROM submissions WHERE id=?", (submission_id,) ).fetchone() if row: old = row["content"] or "" new_content = old.replace( "[🎤 Sprachnachricht — Transkription läuft...]", f"🎤 {text}", ) if new_content == old: # fallback if placeholder not found (e.g. old records) new_content = old + f"\n🎤 Transkription: {text}" conn.execute( "UPDATE submissions SET content=? WHERE id=?", (new_content, submission_id), ) conn.commit() conn.close() except Exception as e: print(f"[transcribe bg error] {e}", file=sys.stderr) # Auto-summarize + hall extraction after transcription auto_process_bg(db_path, submission_id) def transcribe_video(video_path: str) -> str | None: """Extract audio from video, then transcribe. Returns text or None.""" import subprocess import tempfile import json WHISPER_PYTHON = "/home/hermes/.hermes/venvs/speech/bin/python" FFMPEG = "/home/hermes/.local/bin/ffmpeg" LD_PATH = "/home/hermes/.local/lib" wav_path = None try: # Extract audio to temp WAV file fd, wav_path = tempfile.mkstemp(suffix=".wav") os.close(fd) result = subprocess.run( [FFMPEG, "-i", video_path, "-vn", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", wav_path, "-y"], capture_output=True, text=True, timeout=60, env={"LD_LIBRARY_PATH": LD_PATH, "PATH": os.environ.get("PATH", "")}, ) if not os.path.exists(wav_path) or os.path.getsize(wav_path) < 100: return None # Now transcribe the extracted audio return transcribe_audio(wav_path) except Exception as e: print(f"[video transcribe error] {e}", file=sys.stderr) return None finally: if wav_path and os.path.exists(wav_path): os.unlink(wav_path) def generate_video_thumbnail(video_path: str) -> str | None: """Extract a thumbnail frame from a video at 1 second. Returns path to .jpg or None.""" import subprocess FFMPEG = "/home/hermes/.local/bin/ffmpeg" thumb_path = video_path + ".thumb.jpg" # Don't regenerate if exists and is not empty if os.path.exists(thumb_path) and os.path.getsize(thumb_path) > 100: return thumb_path try: subprocess.run( [FFMPEG, "-i", video_path, "-ss", "00:00:01", "-vframes", "1", "-q:v", "5", thumb_path, "-y"], capture_output=True, timeout=30, check=True ) if os.path.exists(thumb_path) and os.path.getsize(thumb_path) > 100: return thumb_path except Exception: pass return None def _transcribe_video_bg(db_path: str, submission_id: int, video_path: str): """Background thread: extract audio from video, transcribe, update DB.""" text = transcribe_video(video_path) if text: try: conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row row = conn.execute( "SELECT content FROM submissions WHERE id=?", (submission_id,) ).fetchone() if row: old = row["content"] or "" new_content = old.replace( "[🎬 Video — Transkription läuft...]", f"🎬 {text}", ) if new_content == old: new_content = old + f"\n🎬 Transkription: {text}" conn.execute( "UPDATE submissions SET content=? WHERE id=?", (new_content, submission_id), ) conn.commit() conn.close() except Exception as e: print(f"[video transcribe bg error] {e}", file=sys.stderr) # Auto-summarize + hall extraction after transcription auto_process_bg(db_path, submission_id) # ── Routes ────────────────────────────────────────────────────────── @app.route("/whatsapp/webhook", methods=["GET", "POST"]) def whatsapp_webhook(): """Meta WhatsApp Webhook. GET=verification, POST=incoming messages.""" if request.method == "GET": mode = request.args.get("hub.mode") token = request.args.get("hub.verify_token") challenge = request.args.get("hub.challenge") if mode == "subscribe" and token == VERIFY_TOKEN: return challenge, 200 return "Forbidden", 403 # POST: Incoming message body = request.get_json(force=True, silent=True) if not body: return "OK", 200 try: entries = body.get("entry", []) db = get_db() for entry in entries: changes = entry.get("changes", []) for change in changes: value = change.get("value", {}) contacts = value.get("contacts", [{}]) messages = value.get("messages", []) for msg in messages: sender_id = msg.get("from", "unknown") sender_name = contacts[0].get("profile", {}).get("name", "unknown") msg_type = msg.get("type", "text") content = "" media_path = None spiel_titel = None category_override = None # set to "ausverkauft" for #out if msg_type == "text": content = msg.get("text", {}).get("body", "") elif msg_type == "image": content = msg.get("image", {}).get("caption", "[Bild ohne Text]") media_id = msg.get("image", {}).get("id") if media_id: media_path, _ = download_media(media_id) elif msg_type == "audio": content = "[🎤 Sprachnachricht — Transkription läuft...]" media_id = msg.get("audio", {}).get("id") if media_id: media_path, _ = download_media(media_id) elif msg_type == "video": content = msg.get("video", {}).get( "caption", "[🎬 Video — Transkription läuft...]" ) media_id = msg.get("video", {}).get("id") if media_id: media_path, _ = download_media(media_id) elif msg_type == "document": content = msg.get("document", {}).get( "caption", "[Dokument]" ) media_id = msg.get("document", {}).get("id") if media_id: media_path, _ = download_media(media_id) else: content = f"[{msg_type}]" # GDPR: "Meine Daten löschen" trigger if msg_type == "text" and content.strip().lower() == "meine daten löschen": _gdpr_delete_sender(db, sender_id) if META_TOKEN: try: send_whatsapp_message( sender_id, "✅ Alle deine Daten wurden vollständig und endgültig vom Server gelöscht. " "Es befinden sich keinerlei personenbezogene Daten mehr von dir bei uns. " "Lediglich ein anonymer Vermerk über die erfolgte Löschung bleibt für " "unsere Dokumentationspflicht.", ) except Exception: pass continue # Check for existing thread from this sender (5-min window) recent = db.execute( """SELECT id, content, media_path, halle FROM submissions WHERE channel='whatsapp' AND sender_id=? AND status != 'gdpr_deleted' ORDER BY created_at DESC LIMIT 1""", (sender_id,), ).fetchone() is_new_user = db.execute( "SELECT COUNT(*) FROM submissions WHERE channel='whatsapp' AND sender_id=? AND status != 'gdpr_deleted'", (sender_id,), ).fetchone()[0] == 0 THREAD_WINDOW_MINUTES = 5 thread_found = False if recent: age_check = db.execute( "SELECT (strftime('%s','now') - strftime('%s',created_at)) < ? FROM submissions WHERE id=?", (THREAD_WINDOW_MINUTES * 60, recent["id"]), ).fetchone() if age_check and age_check[0]: thread_found = True if thread_found: # Append to existing thread prefix = { "text": "", "image": "\n[🖼️ Bild] ", "audio": "\n[🎤 Sprachnachricht]", "video": "\n[🎬 Video] ", "document": "\n[📎 Dokument] ", }.get(msg_type, f"\n[{msg_type}] ") new_content = (recent["content"] or "") + prefix + content # Re-extract halle from combined content new_halle = extract_halle(new_content) or recent["halle"] db.execute( "UPDATE submissions SET content=?, halle=?, created_at=CURRENT_TIMESTAMP WHERE id=?", (new_content, new_halle, recent["id"]), ) # Merge media paths (keep all, separated by ||) if media_path: merged_media = media_path if recent["media_path"]: merged_media = recent["media_path"] + "||" + media_path db.execute( "UPDATE submissions SET media_path=? WHERE id=?", (merged_media, recent["id"]), ) else: # New thread halle = extract_halle(content) cat = category_override or None db.execute( """INSERT INTO submissions (channel, sender_id, sender_name, type, content, media_path, status, halle, category, spiel_titel) VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?)""", ("whatsapp", sender_id, sender_name, msg_type, content, media_path, halle, cat, spiel_titel), ) # Auto-reply: only on NEW threads, not appends if not thread_found and META_TOKEN: try: if is_new_user: reply = ( f"🎲 Hey {sender_name}! Cool, dass du da bist — willkommen in der " f"brettspiel-news.de Community!\n\n" f"Schick uns einfach, was dich bewegt: 💬 Text, 🎤 Sprachnachrichten, " f"🖼️ Fotos oder 🎬 Videos — alles rund um Brettspiele. Wir schauen uns alles an!\n\n" f"✍️ Nenn uns deinen Vornamen, wenn du magst — das ist deine Einwilligung, " f"dass wir dich als Quelle nennen, falls wir deinen Beitrag verwenden. " f"Ohne Namen schreiben wir „anonyme Quelle\".\n\n" f"🔒 „Meine Daten löschen\" genügt — und alles ist sofort und vollständig weg." ) send_whatsapp_message(sender_id, reply) # Thorsten voice: same text but without emojis import re tts_text = re.sub(r'[^\x00-\x7F\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u1E00-\u1EFF\u2018-\u201F\u2013\u2014\u2026\u00A0\u00AB\u00BB\u20AC]', '', reply) tts_text = re.sub(r' +', ' ', tts_text).strip() audio_path = generate_thorsten_tts(tts_text) if audio_path: media_id = upload_whatsapp_media(audio_path) if media_id: send_whatsapp_audio(sender_id, media_id) else: reply = ( f"📨 Danke {sender_name}! Ist angekommen — " f"wir kümmern uns drum." ) send_whatsapp_message(sender_id, reply) except Exception: pass # Transcribe audio in background (non-blocking) if msg_type == "audio" and media_path: import threading sub_id = recent["id"] if thread_found else db.execute( "SELECT last_insert_rowid()" ).fetchone()[0] threading.Thread( target=_transcribe_bg, args=(DB_PATH, sub_id, media_path), daemon=True, ).start() # Transcribe video in background if msg_type == "video" and media_path: import threading sub_id = recent["id"] if thread_found else db.execute( "SELECT last_insert_rowid()" ).fetchone()[0] threading.Thread( target=_transcribe_video_bg, args=(DB_PATH, sub_id, media_path), daemon=True, ).start() # Auto-summarize text/image submissions (LLM hall + summary) if msg_type in ("text", "image") and not thread_found and content and len(content.strip()) > 20: import threading sub_id = db.execute("SELECT last_insert_rowid()").fetchone()[0] # Only if regex didn't already find a halle if not halle: threading.Thread( target=auto_process_bg, args=(DB_PATH, sub_id), daemon=True, ).start() db.commit() except Exception as e: print(f"[webhook error] {e}", file=sys.stderr) return "OK", 200 return "OK", 200 @app.route("/admin") def admin_dashboard(): """Internal admin dashboard — filterable table of all submissions.""" db = get_db() channel_filter = request.args.get("channel", "") status_filter = request.args.get("status", "") category_filter = request.args.get("category", "") search = request.args.get("q", "") halle_filter = request.args.get("halle", "") query = "SELECT * FROM submissions WHERE deleted_at IS NULL" params: list = [] if channel_filter: query += " AND channel = ?" params.append(channel_filter) if status_filter: query += " AND status = ?" params.append(status_filter) if category_filter: query += " AND category = ?" params.append(category_filter) if halle_filter: query += " AND halle = ?" params.append(halle_filter) if search: query += " AND (content LIKE ? OR sender_id LIKE ?)" params.extend([f"%{search}%", f"%{search}%"]) query += " ORDER BY created_at DESC LIMIT 200" rows = db.execute(query, params).fetchall() channels = [ r[0] for r in db.execute("SELECT DISTINCT channel FROM submissions").fetchall() ] statuses = [ r[0] for r in db.execute("SELECT DISTINCT status FROM submissions").fetchall() ] categories = [ r[0] for r in db.execute( "SELECT DISTINCT category FROM submissions WHERE category IS NOT NULL" ).fetchall() ] # Channel totals totals_html = "".join( f'
' f'{db.execute("SELECT COUNT(*) FROM submissions WHERE channel=?", (c,)).fetchone()[0]}' f" {c}
" for c in channels ) # Get distinct halle values hallen = [r[0] for r in db.execute("SELECT DISTINCT halle FROM submissions WHERE halle IS NOT NULL ORDER BY halle").fetchall()] # Filter dropdowns filter_html = f"""
Reset
""" # Status display names (German) status_names = { "new": "Neu", "processing": "In Bearbeitung", "done": "Erledigt", "flagged": "Markiert", "gdpr_deleted": "DSGVO-Gelöscht", "published": "Veröffentlicht", } table_rows = "" for r in rows: content_snippet = (r["content"] or "")[:120] media_icon = {"image": "🖼️", "audio": "🎤", "video": "🎬", "document": "📎"}.get(r["type"], "") st_label = status_names.get(r["status"], r["status"]) table_rows += f""" {r['id']} {r['channel']} {r['sender_id']} {media_icon} {r['type']} {r['halle'] or '-'} {r['spiel_titel'] or '-'} {content_snippet} {st_label} {r['category'] or '-'} {r['created_at']} """ # Mobile card rows mobile_cards = "" for r in rows: content_snippet = (r["content"] or "")[:80] media_icon = {"image": "🖼️", "audio": "🎤", "video": "🎬", "document": "📎"}.get(r["type"], "") st_label = status_names.get(r["status"], r["status"]) mobile_cards += f"""
#{r['id']}{st_label}
{media_icon} {r['type']} · {r['channel']}
{'📍 ' + r['halle'] if r['halle'] else ''}{' 🎯 ' + r['spiel_titel'] if r['spiel_titel'] else ''}
{content_snippet}
{r['created_at']}
""" return f""" BSN Intake — Admin
Brettspiel News
{totals_html}
{filter_html.replace('
','').replace('style="padding:0.3rem 0.8rem;"','').replace('style="padding:0.3rem;"','').replace('onchange="this.form.submit()"','onchange="this.form.submit()"')}
Reset
{mobile_cards}
{table_rows}
IDSenderTyp📍 Halle🎯 SpielInhaltStatusZeit
""".replace("{LOGO_B64}", LOGO_B64) @app.route("/test") def test_page(): """Page with a curl snippet for simulated testing.""" return """ Simulierter Test Brettspiel News

🧪 Simulierter WhatsApp Test

Dieser curl-Befehl sendet eine Fake-WhatsApp-Nachricht an den lokalen Webhook:

curl -X POST http://127.0.0.1:5002/whatsapp/webhook \\
  -H "Content-Type: application/json" \\
  -d '{
    "object": "whatsapp_business_account",
    "entry": [{
      "id": "TEST",
      "changes": [{
        "value": {
          "messaging_product": "whatsapp",
          "metadata": {
            "display_phone_number": "TEST",
            "phone_number_id": "TEST"
          },
          "contacts": [{
            "profile": {"name": "Max Mustermann"}
          }],
          "messages": [{
            "from": "+491234567890",
            "id": "wamid.test123",
            "timestamp": "1718550000",
            "type": "text",
            "text": {"body": "Hallo! Gibt es Catan noch in Halle 5?"}
          }]
        }
      }]
    }]
  }'

← Zurück zum Dashboard

""".replace("{LOGO_B64}", LOGO_B64) @app.route("/health") def health(): return jsonify( { "status": "ok", "db": str(DB_PATH), "whatsapp_configured": bool(META_TOKEN), "phone_number_id": PHONE_NUMBER_ID, } ) @app.route("/submission/") def submission_detail(sub_id: int): """Detail view for a single submission.""" db = get_db() row = db.execute("SELECT * FROM submissions WHERE id = ?", (sub_id,)).fetchone() if not row: return "

Nicht gefunden

← Zurück

", 404 import base64 # Media preview with per-item publish checkboxes media_html = "" if row["media_path"]: paths = row["media_path"].split("||") # Parse existing publish flags flags_str = row["media_publish_flags"] or "" flags = flags_str.split(",") if flags_str else ["1"] * len(paths) # Pad flags if needed while len(flags) < len(paths): flags.append("1") mime_map = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp", ".gif": "image/gif", ".mp4": "video/mp4", ".webm": "video/webm", ".ogg": "audio/ogg", ".mp3": "audio/mpeg", ".wav": "audio/wav", } for i, path in enumerate(paths): checked = "checked" if flags[i] == "1" else "" if not os.path.exists(path): media_html += f'

📁 Datei nicht gefunden: {os.path.basename(path)}

' continue _, ext = os.path.splitext(path) mime = mime_map.get(ext.lower(), "application/octet-stream") try: with open(path, "rb") as f: b64 = base64.b64encode(f.read()).decode() src = f"data:{mime};base64,{b64}" media_html += f'
' # Media element if mime.startswith("image/"): media_html += f'' elif mime.startswith("video/"): # Generate thumbnail for poster thumb = generate_video_thumbnail(path) poster_attr = "" if thumb: try: with open(thumb, "rb") as tf: thumb_b64 = base64.b64encode(tf.read()).decode() poster_attr = f' poster="data:image/jpeg;base64,{thumb_b64}"' except Exception: pass media_html += f'' elif mime.startswith("audio/"): media_html += f'' else: media_html += f'

📎 {os.path.basename(path)}

' # Toggle bar media_html += f'
' media_html += f'' media_html += f'Veröffentlichen' media_html += f'
' media_html += f'
' except Exception as e: media_html += f'

Fehler: {e}

' # SVG type icons (16x16, BSN blue) _svg_icons = { "text": '', "image": '', "audio": '', "video": '', "document": '', } type_emoji = _svg_icons.get(row["type"], _svg_icons["document"]) return f""" #{row['id']} — BSN
← Zurück

{type_emoji} #{row['id']}

{row['channel']} · {row['created_at']}
{f'
📍 {row["halle"]}
' if row['halle'] else ''}
{"".join([f'' if row['status'] == 'published' else ''])}
Absender
{row['sender_name'] or row['sender_id']}({row['sender_id']})
Typ
{type_emoji} {row['type']}
Inhalt
{row['content'] or '(leer)'}
Status
{row['status']}
Kategorie
{row['category'] or '-'}
📍 Halle / Stand
{row['halle'] or '– nicht erkannt –'}
🎯 Spieltitel
✍️ Für Frontend-Veröffentlichung
📍 Halle / Stand
🎯 Spieltitel
Zusammenfassung (LLM/Mensch)
Name zur Veröffentlichung
🖼️ Medien verwalten

Nur Medien mit Haken erscheinen im Frontend.

{media_html}
🗑️ Gefahrenzone

Löscht den Eintrag und alle Mediendateien unwiderruflich.

""" @app.route("/submission//update-summary", methods=["POST"]) def submission_update_summary(sub_id: int): """Update summary, publish_name, and change status.""" db = get_db() action = request.form.get("action", "save") summary = request.form.get("summary", "").strip() publish_name = request.form.get("publish_name", "").strip() halle = request.form.get("halle", "").strip() spiel_titel = request.form.get("spiel_titel", "").strip() if action == "publish": db.execute( "UPDATE submissions SET status='published', published_at=CURRENT_TIMESTAMP WHERE id=?", (sub_id,), ) elif action == "done": db.execute("UPDATE submissions SET status='done' WHERE id=?", (sub_id,)) elif action == "save": # Partial update — only update fields that are provided fields = [] params = [] if summary: fields.append("summary=?") params.append(summary) if publish_name: fields.append("publish_name=?") params.append(publish_name) if halle: fields.append("halle=?") params.append(halle) if spiel_titel: fields.append("spiel_titel=?") params.append(spiel_titel) # Clear fields explicitly set to empty if request.form.get("summary") is not None and not summary: fields.append("summary=NULL") if request.form.get("publish_name") is not None and not publish_name: fields.append("publish_name=NULL") if request.form.get("halle") is not None and not halle: fields.append("halle=NULL") if request.form.get("spiel_titel") is not None and not spiel_titel: fields.append("spiel_titel=NULL") if fields: params.append(sub_id) db.execute(f"UPDATE submissions SET {', '.join(fields)} WHERE id=?", params) elif action == "save_media": # Collect media publish flags from form import re flags_dict = {} for key in request.form: m = re.match(r'media_publish_(\d+)', key) if m: flags_dict[int(m.group(1))] = "1" # Get current media paths count row = db.execute("SELECT media_path FROM submissions WHERE id=?", (sub_id,)).fetchone() if row and row["media_path"]: n = len(row["media_path"].split("||")) flags = [flags_dict.get(i, "0") for i in range(n)] db.execute( "UPDATE submissions SET media_publish_flags=? WHERE id=?", (",".join(flags), sub_id), ) db.commit() return """""" @app.route("/submission//unpublish", methods=["POST"]) def submission_unpublish(sub_id: int): """Unpublish a submission - set status back to 'new' and clear published_at.""" db = get_db() row = db.execute("SELECT id, status FROM submissions WHERE id = ?", (sub_id,)).fetchone() if not row: return jsonify({"error": "Nicht gefunden"}), 404 if row["status"] != "published": return jsonify({"error": "Beitrag ist nicht veroffentlicht"}), 400 db.execute("UPDATE submissions SET status='new', published_at=NULL WHERE id=?", (sub_id,)) db.commit() return jsonify({"ok": True, "new_status": "new"}) @app.route("/submission//done", methods=["POST"]) def submission_done(sub_id: int): """Mark a submission as done/bearbeitet.""" db = get_db() db.execute("UPDATE submissions SET status='done' WHERE id=?", (sub_id,)) db.commit() return """
✅ Beitrag als erledigt markiert
""" @app.route("/submission//generate-summary", methods=["POST"]) def generate_llm_summary(sub_id: int): """Call DeepSeek V4 Flash to summarize the submission content.""" db = get_db() row = db.execute("SELECT * FROM submissions WHERE id = ?", (sub_id,)).fetchone() if not row: return jsonify({"error": "Nicht gefunden"}), 404 content = (row["content"] or "").strip() if not content: return jsonify({"summary": ""}) # Truncate very long content if len(content) > 3000: content = content[:3000] + "..." import requests as req import yaml # Read API key from Hermes config config_path = os.path.expanduser("~/.hermes/config.yaml") api_key = "" try: with open(config_path) as f: cfg = yaml.safe_load(f) api_key = cfg.get("providers", {}).get("datenhimmel", {}).get("api_key", "") except Exception: pass if not api_key: return jsonify({"error": "API-Key nicht konfiguriert"}), 500 prompt = ( "Du bist Redaktionsassistent für brettspiel-news.de. " "Fasse folgende Community-Nachricht in 2-3 Sätzen zusammen. " "Schreibe im Präsens, journalistisch, ohne Floskeln. " "Kein 'Der Nutzer schreibt...' oder 'In dieser Nachricht...'. " "Maximal 250 Zeichen.\n\n" f"Nachricht:\n{content}" ) try: r = req.post( "https://ui.datenhimmel.work/api/chat/completions", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, json={ "model": "deepseek-v4-flash:cloud", "messages": [{"role": "user", "content": prompt}], "max_tokens": 300, "temperature": 0.3, }, timeout=30, ) if r.status_code == 200: data = r.json() summary = data["choices"][0]["message"]["content"].strip() return jsonify({"summary": summary}) else: return jsonify({"error": f"API-Fehler: {r.status_code}"}), 500 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/submission//delete", methods=["POST"]) def submission_delete(sub_id: int): """Delete a submission and its media file.""" db = get_db() row = db.execute("SELECT media_path FROM submissions WHERE id = ?", (sub_id,)).fetchone() if row and row["media_path"]: for p in row["media_path"].split("||"): if os.path.exists(p): os.remove(p) db.execute("DELETE FROM submissions WHERE id = ?", (sub_id,)) db.commit() return f"""
🗑️ Beitrag #{sub_id} gelöscht
""" @app.route("/") def public_frontend(): """Public frontend showing published submissions — SPIEL Essen inspired.""" import base64 db = get_db() # Filters halle_filter = request.args.get("halle", "").strip() cat_filter = request.args.get("cat", "").strip() query = "SELECT * FROM submissions WHERE status='published' AND deleted_at IS NULL" params = [] if halle_filter: query += " AND halle LIKE ?" params.append(f"{halle_filter}%") if cat_filter: query += " AND category = ?" params.append(cat_filter) query += " ORDER BY published_at DESC LIMIT 50" rows = db.execute(query, params).fetchall() # Get available filter values — group by hall number (H1-H7) hallen = [] hall_rows = db.execute( "SELECT DISTINCT halle FROM submissions WHERE halle IS NOT NULL AND status='published' ORDER BY halle" ).fetchall() seen = set() for r in hall_rows: hnum = r[0][:2] if r[0] else "" if hnum and hnum[0] == 'H' and hnum[1:].isdigit() and hnum not in seen: seen.add(hnum) hallen.append(hnum) categories = [r[0] for r in db.execute( "SELECT DISTINCT category FROM submissions WHERE category IS NOT NULL AND status='published' ORDER BY category" ).fetchall()] empty_state = """ BSN Community — Stimmen aus der Szene

Bald geht's los!

Noch keine ver\u00f6ffentlichten Beitr\u00e4ge. Komm bald wieder — die Community f\u00fcllt diesen Ort mit spannenden Geschichten.

""" if not rows: return empty_state # Build filter bar HTML — hallen as dropdown filter_html = '
📍 Halle: ' filter_html += '
' if cat_filter: filter_html += f'' filter_html += '
' if categories: filter_html += '📂 Kategorie: ' filter_html += '
' if halle_filter: filter_html += f'' filter_html += '
' if halle_filter or cat_filter: filter_html += '✕ Filter zurücksetzen' filter_html += "
" cards = "" for r in rows: media_html = "" media_first = "" if r["media_path"]: paths = r["media_path"].split("||") # Parse publish flags flags_str = r["media_publish_flags"] or "" flags = flags_str.split(",") if flags_str else ["1"] * len(paths) while len(flags) < len(paths): flags.append("1") mime_map = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp", ".gif": "image/gif", ".mp4": "video/mp4", ".webm": "video/webm", ".ogg": "audio/ogg", ".mp3": "audio/mpeg", ".wav": "audio/wav", } for i, path in enumerate(paths): # Skip media not marked for publishing if flags[i] != "1": continue if not os.path.exists(path): continue _, ext = os.path.splitext(path) mime = mime_map.get(ext.lower(), "application/octet-stream") try: with open(path, "rb") as f: b64 = base64.b64encode(f.read()).decode() src = "data:" + mime + ";base64," + b64 if mime.startswith("image/"): img_tag = ( '' '
' 'Beitragsbild' '
' ) if i == 0 and not media_first: media_first = img_tag else: media_html += img_tag elif mime.startswith("video/"): thumb = generate_video_thumbnail(path) if thumb: with open(thumb, "rb") as f: thumb_b64 = base64.b64encode(f.read()).decode() thumb_src = "data:image/jpeg;base64," + thumb_b64 else: thumb_src = "" vid_tag = ( '' '
' + (f'Video-Vorschau' if thumb_src else '
🎬
') + '
' + '' + '
' ) if i == 0 and not media_first: media_first = vid_tag else: media_html += vid_tag elif mime.startswith("audio/"): aud_tag = '
🎤 Sprachnachricht anhören →
' media_html += aud_tag except Exception: pass _fe_svg_icons = { "text": '', "image": '', "audio": '', "video": '', "document": '', } type_emoji = _fe_svg_icons.get(r["type"], _fe_svg_icons["document"]) name = (r["publish_name"] or r["sender_name"] or "Anonym").strip() if not name or name.lower() in ("unknown", "anonym"): name = "Anonym" summary_text = (r["summary"] or r["content"] or "") # Truncate for card preview preview = summary_text[:200] + ("..." if len(summary_text) > 200 else "") date_str = (r["published_at"] or r["created_at"] or "") # Format with relative + absolute time time_html = "" try: from datetime import datetime dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") now = datetime.now() diff = now - dt if diff.days == 0: if diff.seconds < 3600: time_html = f'vor {diff.seconds // 60} Min.' else: time_html = f'heute {dt.strftime("%H:%M")} Uhr' elif diff.days == 1: time_html = f'gestern {dt.strftime("%H:%M")} Uhr' elif diff.days < 7: time_html = f'vor {diff.days} Tagen {dt.strftime("%H:%M")}' else: time_html = dt.strftime("%d.%m.%y %H:%M") + " Uhr" date_str = dt.strftime("%d. %B %Y") except: pass text_only_class = " text-only" if not media_first else "" time_block = "" if time_html: time_overlay = f'
{time_html}
' time_block = time_overlay # Always overlay now elif not media_first: time_block = '' if not media_first: # Text-only card: link-wrapped, time overlay inside card-content cards += f"""
""" + (f'
📍 {r["halle"]}
' if r["halle"] else '') + """ """ + (f'
🚫 AUSVERKAUFT: {r["spiel_titel"]}
' if r["category"] == "ausverkauft" and r["spiel_titel"] else '') + """
""" + time_block + """

""" + preview + """

""" else: # Media card: existing structure cards += f"""
""" + media_first + time_block + """ """ + (f'
📍 {r["halle"]}
' if r["halle"] else '') + """ """ + (f'
🚫 AUSVERKAUFT: {r["spiel_titel"]}
' if r["category"] == "ausverkauft" and r["spiel_titel"] else '') + """

""" + preview + """

""" return (""" BSN Community — Stimmen aus der Szene """ + filter_html + """
""" + cards + """
💬 BSN Assistant
Hey! 👋 Ich bin der BSN Community Assistant. Frag mich nach Spielen, Ständen oder was auf der SPIEL Essen los ist!
📨 Etwas einreichen
🎲 Schick uns, was dich bewegt! Texte, Fotos, Videos oder Sprachnachrichten — alles rund um Brettspiele und die SPIEL Essen. Wir schauen uns alles an und veröffentlichen die spannendsten Beiträge.
Nur Text oder nur eine Datei reicht völlig.
Foto
Video
Audio
""").replace("{LOGO_B64}", LOGO_B64) @app.route('/api/query') def api_query(): '''Instant lookup: find submissions by hall, keyword, or sender.''' db = get_db() q = request.args.get('q', '').strip() halle = request.args.get('halle', '').strip() limit = min(int(request.args.get('limit', 5)), 20) query = 'SELECT id, halle, content, summary, type, sender_name, status FROM submissions WHERE status="published"' params = [] if q: query += ' AND (content LIKE ? OR summary LIKE ?)' params.extend([f'%{q}%', f'%{q}%']) if halle: query += ' AND halle = ?' params.append(halle) query += ' ORDER BY published_at DESC LIMIT ?' params.append(limit) rows = db.execute(query, params).fetchall() results = [] for r in rows: results.append({ 'id': r['id'], 'halle': r['halle'], 'content': (r['summary'] or r['content'] or '')[:200], 'type': r['type'], 'sender_name': r['sender_name'] or 'Anonym', 'status': r['status'], }) return jsonify({'results': results, 'count': len(results)}) @app.route('/api/chat', methods=['POST']) def api_chat(): """Chat agent with read-only access to published submissions.""" db = get_db() data = request.get_json(force=True, silent=True) or {} user_message = (data.get('message') or '').strip() if not user_message: return jsonify({'reply': 'Leere Nachricht. Was möchtest du wissen?'}) # Build context from published submissions rows = db.execute( "SELECT id, halle, content, summary, type, sender_name, category, " "spiel_titel, published_at " "FROM submissions WHERE status='published' AND deleted_at IS NULL " "ORDER BY published_at DESC LIMIT 60" ).fetchall() if not rows: return jsonify({'reply': 'Noch keine Beiträge veröffentlicht. Schau später wieder vorbei!'}) # Build compact context string context_parts = [] for i, r in enumerate(rows, 1): text = (r['summary'] or r['content'] or '').strip()[:400] halle = r['halle'] or '' spiel = r['spiel_titel'] or '' cat = r['category'] or '' name = (r['sender_name'] or 'Anonym').strip() context_parts.append( f"[{i}] {text}" + (f" | 📍{halle}" if halle else '') + (f" | 🎲{spiel}" if spiel else '') + (f" | #{cat}" if cat else '') + (f" | von {name}" if name else '') ) context = '\n'.join(context_parts) # System prompt system = ( "Du bist der BSN Community Assistant von brettspiel-news.de. " "Du hilfst Besuchern, Informationen über die Brettspiel-Community auf der SPIEL Essen zu finden.\n\n" "WICHTIGE REGELN:\n" "- Beantworte Fragen NUR mit Informationen aus dem Kontext unten.\n" "- Wenn die Antwort nicht im Kontext steht, sage ehrlich: 'Dazu habe ich leider keine Informationen.'\n" "- Erfinde NIEMALS Informationen, Namen, Stände oder Spieletitel.\n" "- Antworte immer auf Deutsch, freundlich und hilfsbereit.\n" "- Halte Antworten kurz und präzise (2-4 Sätze).\n" "- Bei Standortfragen nenne immer die Hallen-Nummer.\n" "- Der Kontext enthält nummerierte Beiträge [1], [2] usw. — du kannst darauf verweisen.\n\n" f"KONTEXT (veröffentlichte Community-Beiträge):\n{context[:8000]}" ) # Read API key config_path = os.path.expanduser("~/.hermes/config.yaml") api_key = "" try: with open(config_path) as f: import yaml as _yaml cfg = _yaml.safe_load(f) api_key = cfg.get("providers", {}).get("datenhimmel", {}).get("api_key", "") except Exception: pass if not api_key: return jsonify({'reply': 'Chat-Assistent ist aktuell nicht verfügbar (API-Key fehlt).'}) # Build messages with conversation history messages = [{"role": "system", "content": system}] history = data.get('history') or [] if isinstance(history, list): for h in history[-10:]: # last 10 messages max if isinstance(h, dict) and 'role' in h and 'content' in h: messages.append({"role": h['role'], "content": h['content']}) messages.append({"role": "user", "content": user_message}) try: r = requests.post( "https://ui.datenhimmel.work/api/chat/completions", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json={"model": "deepseek-v4-flash:cloud", "messages": messages, "max_tokens": 400, "temperature": 0.4}, timeout=15, ) if r.status_code != 200: return jsonify({'reply': 'Der Chat-Assistent antwortet gerade nicht. Bitte versuch es später nochmal.'}) reply = r.json()["choices"][0]["message"]["content"].strip() except Exception: return jsonify({'reply': 'Der Chat-Assistent antwortet gerade nicht. Bitte versuch es später nochmal.'}) return jsonify({'reply': reply}) @app.route('/api/submit', methods=['POST']) def api_submit(): """Public submission endpoint — text + optional media upload.""" db = get_db() name = (request.form.get('name') or '').strip()[:100] text = (request.form.get('text') or '').strip()[:5000] halle = (request.form.get('halle') or '').strip()[:20] spiel_titel = (request.form.get('spiel_titel') or '').strip()[:200] # Require at least text or a file file = request.files.get('media') if not text and (not file or not file.filename): return jsonify({'error': 'Bitte Text oder eine Datei (Foto/Video/Audio) angeben.'}), 400 media_path = None msg_type = 'text' if file and file.filename: # Determine type from extension ext = os.path.splitext(file.filename)[1].lower() mime_map = { '.jpg': 'image', '.jpeg': 'image', '.png': 'image', '.webp': 'image', '.gif': 'image', '.mp4': 'video', '.webm': 'video', '.ogg': 'audio', '.mp3': 'audio', '.wav': 'audio', } msg_type = mime_map.get(ext, 'document') # Add type prefix to content prefix = { 'image': '\n[🖼️ Bild]', 'video': '\n[🎬 Video]', 'audio': '\n[🎤 Sprachnachricht]', 'document': '\n[📎 Dokument]', }.get(msg_type, f'\n[{msg_type}]') if text: text = text + prefix else: text = prefix.strip() # Save file ts = datetime.now().strftime('%Y%m%d_%H%M%S') safe_name = f"web_{ts}{ext}" save_path = MEDIA_DIR / safe_name try: file.save(str(save_path)) media_path = str(save_path) except Exception: return jsonify({'error': 'Datei konnte nicht gespeichert werden.'}), 500 # Validate halle halle_valid = None if halle: import re if re.match(r'^H[1-7]\s*[A-Z]\d{1,4}$', halle, re.IGNORECASE): halle_valid = halle.upper() sender_id = 'web_' + (name.replace(' ', '_') if name else 'anonym') sender_name = name or None db.execute( """INSERT INTO submissions (channel, sender_id, sender_name, type, content, media_path, status, halle, spiel_titel) VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?)""", ('web', sender_id, sender_name, msg_type, text or None, media_path, halle_valid, spiel_titel or None), ) db.commit() sub_id = db.execute("SELECT last_insert_rowid()").fetchone()[0] # Auto-summarize via LLM (only if no halle was provided by user) if not halle_valid and text and len(text.strip()) > 20: import threading threading.Thread( target=auto_process_bg, args=(DB_PATH, sub_id), daemon=True, ).start() return jsonify({ 'ok': True, 'message': f'Beitrag #{sub_id} erfolgreich übermittelt! 🎲', 'submission_id': sub_id, 'delete_hint': 'Notiere dir diese ID — mit ihr kannst du den Beitrag später löschen lassen.', }) @app.route("/beitrag/") def public_detail(sub_id: int): """Public detail view — full media, text, and metadata.""" import base64 db = get_db() row = db.execute( "SELECT * FROM submissions WHERE id = ? AND status='published' AND deleted_at IS NULL", (sub_id,), ).fetchone() if not row: return "

Nicht gefunden

← Zurück

", 404 # Build media HTML — videos are playable, images full-size media_html = "" if row["media_path"]: paths = row["media_path"].split("||") flags_str = row["media_publish_flags"] or "" flags = flags_str.split(",") if flags_str else ["1"] * len(paths) while len(flags) < len(paths): flags.append("1") mime_map = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp", ".gif": "image/gif", ".mp4": "video/mp4", ".webm": "video/webm", ".ogg": "audio/ogg", ".mp3": "audio/mpeg", ".wav": "audio/wav", } for i, path in enumerate(paths): if flags[i] != "1": continue if not os.path.exists(path): continue _, ext = os.path.splitext(path) mime = mime_map.get(ext.lower(), "application/octet-stream") try: with open(path, "rb") as f: b64 = base64.b64encode(f.read()).decode() src = "data:" + mime + ";base64," + b64 if mime.startswith("image/"): media_html += f'Bild' elif mime.startswith("video/"): media_html += ( f'' ) elif mime.startswith("audio/"): media_html += f'' except Exception: pass type_emoji = {"text": "💬", "image": "🖼️", "audio": "🎤", "video": "🎬", "document": "📎"}.get(row["type"], "📌") name = (row["publish_name"] or row["sender_name"] or "Anonym").strip() if not name or name.lower() in ("unknown", "anonym"): name = "Anonym" date_str = row["published_at"] or row["created_at"] or "" try: from datetime import datetime as dt d = dt.strptime(date_str, "%Y-%m-%d %H:%M:%S") date_str = d.strftime("%d. %B %Y") except Exception: pass content = (row["summary"] or row["content"] or "").strip() return f""" Beitrag — BSN Community
← Zurück zur Übersicht
{"".join([f'
📍 {row["halle"]}
' if row["halle"] else '', f'
🚫 AUSVERKAUFT: {row["spiel_titel"]}
' if row["category"] == "ausverkauft" and row["spiel_titel"] else ''])}
{type_emoji} {name} {date_str}
{media_html}
{"".join([f'
{content}
' if content else ''])}
Beitrags-ID: #{sub_id} \u2014 Bei Fragen oder Löschwunsch bitte diese ID angeben.
""" if __name__ == "__main__": import threading def keepalive(): """Prevent process from being killed by inactivity timeout.""" import time while True: time.sleep(60) import sys sys.stdout.write("[keepalive]\n") sys.stdout.flush() threading.Thread(target=keepalive, daemon=True).start() print(f"🚀 BSN Intake starting on http://127.0.0.1:{PORT}") print(f" DB: {DB_PATH}") print(f" Media: {MEDIA_DIR}") print(f" Meta API: {'✅ configured' if META_TOKEN else '❌ no token'}") print(f" Admin: http://127.0.0.1:{PORT}/admin") print(f" Webhook: http://127.0.0.1:{PORT}/whatsapp/webhook") app.run(host="127.0.0.1", port=PORT, debug=True)