From 19ddbeb4a2b6cd996f3182174993a5c8dfcf7812 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Thu, 10 May 2018 12:09:26 -0300 Subject: [PATCH 01/32] [ADD] Work in progress Co-authored-by: Matheus --- projects/migrations/0001_initial.py | 26 -------------------------- tags/views.py | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 27 deletions(-) delete mode 100644 projects/migrations/0001_initial.py diff --git a/projects/migrations/0001_initial.py b/projects/migrations/0001_initial.py deleted file mode 100644 index 5949ab7..0000000 --- a/projects/migrations/0001_initial.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 2.0.3 on 2018-04-09 20:52 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Project', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='Nome')), - ('description', models.TextField(verbose_name='Descrição')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/tags/views.py b/tags/views.py index 377d024..a2fd6a5 100644 --- a/tags/views.py +++ b/tags/views.py @@ -9,18 +9,24 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.generics import ListAPIView @permission_classes((IsAuthenticatedOrReadOnly, )) -class TagList(APIView): +class TagList(ListAPIView): authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) def get(self, request, format=None): tags = Tag.objects.all() serializer = TagSerializer(tags, many=True) + + filter_backends = (DjangoFilterBackend, ) return Response(serializer.data) + + def post(self, request, format=None): serializer = TagSerializer(data=request.data) if serializer.is_valid(): @@ -28,6 +34,18 @@ def post(self, request, format=None): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) +# @permission_classes((IsAuthenticatedOrReadOnly, )) +# class TagSeach(ListAPIView): +# serializer = TagSerializer + +# def get_queryset(self): + +# queryset = Purchase.objects.all() +# username = self.request.query_params.get('username', None) +# if username is not None: +# queryset = queryset.filter(purchaser__username=username) +# return queryset + @permission_classes((IsAuthenticated, )) class TagDetail(APIView): From b1d0c64fd076841b4f9606ba0231d481792cfe77 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Thu, 10 May 2018 19:51:58 -0300 Subject: [PATCH 02/32] [ADD] adding search contructor for look projects by tags Co-authored-by: Joao Pedro --- .gitignore | 1 + projects/models.py | 2 +- projects/views.py | 15 ++++++++++++++- requirements.txt | 4 +++- tags/views.py | 20 +------------------- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index da597e7..43ecf5f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ wheels/ *.egg-info/ .installed.cfg *.egg +*migrations* # PyInstaller # Usually these files are written by a python script from a template diff --git a/projects/models.py b/projects/models.py index 870fba6..4cf859d 100644 --- a/projects/models.py +++ b/projects/models.py @@ -4,7 +4,7 @@ from tags.models import Tag -class Project(models.Model): # Herdando da model +class Project(models.Model): name = models.CharField(max_length=80, validators=[MinLengthValidator(5)], blank=False, verbose_name="Nome") diff --git a/projects/views.py b/projects/views.py index e356498..3507baa 100644 --- a/projects/views.py +++ b/projects/views.py @@ -10,12 +10,25 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication +from rest_framework import generics +from rest_framework import filters @permission_classes((IsAuthenticatedOrReadOnly,)) -class ProjectList(APIView): +class ProjectList(generics.ListAPIView): authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) + queryset = Project.objects.all() + serializer_class = ProjectSerializer + filter_backends = (filters.SearchFilter, ) + search_fields = ('=tags__name', '=tags__slug') + + # def get_queryset(self): + # tag_name = self.request.query_params.get('tag_name', None) + # queryset = Project.objects.all() + # if tag_name is not None: + # queryset = queryset.filter(tags__name=tag_name) + # return queryset def get(self, request, format=None): projects = Project.objects.all() diff --git a/requirements.txt b/requirements.txt index 9de135d..06a82a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,6 @@ djangorestframework-jwt model_mommy coverage PyYAML -mongoengine \ No newline at end of file +mongoengine +pandas +django-filter \ No newline at end of file diff --git a/tags/views.py b/tags/views.py index a2fd6a5..377d024 100644 --- a/tags/views.py +++ b/tags/views.py @@ -9,24 +9,18 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication -from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.generics import ListAPIView @permission_classes((IsAuthenticatedOrReadOnly, )) -class TagList(ListAPIView): +class TagList(APIView): authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) def get(self, request, format=None): tags = Tag.objects.all() serializer = TagSerializer(tags, many=True) - - filter_backends = (DjangoFilterBackend, ) return Response(serializer.data) - - def post(self, request, format=None): serializer = TagSerializer(data=request.data) if serializer.is_valid(): @@ -34,18 +28,6 @@ def post(self, request, format=None): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -# @permission_classes((IsAuthenticatedOrReadOnly, )) -# class TagSeach(ListAPIView): -# serializer = TagSerializer - -# def get_queryset(self): - -# queryset = Purchase.objects.all() -# username = self.request.query_params.get('username', None) -# if username is not None: -# queryset = queryset.filter(purchaser__username=username) -# return queryset - @permission_classes((IsAuthenticated, )) class TagDetail(APIView): From 61d3e60b2441e1e0167f796333307242e2bcfa3e Mon Sep 17 00:00:00 2001 From: jppgomes Date: Fri, 11 May 2018 23:26:01 -0300 Subject: [PATCH 03/32] [FIX] fix urls with incorrect sintax Co-authored-by: andre Co-authored-by: Yoshida --- projects/urls.py | 2 +- users/urls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/urls.py b/projects/urls.py index a09eca7..6dd37f1 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -6,5 +6,5 @@ urlpatterns = [ path('', views.ProjectList.as_view(), name='projects'), - path('/', views.ProjectDetail.as_view(), name='projects-detail'), + path('/', views.ProjectDetail.as_view(), name='project-detail'), ] diff --git a/users/urls.py b/users/urls.py index a959a8c..9c2b4f2 100644 --- a/users/urls.py +++ b/users/urls.py @@ -6,5 +6,5 @@ urlpatterns = [ path('', views.UserList.as_view(), name='users'), - path('/', views.UserDetail.as_view(), name='users-detail'), + path('/', views.UserDetail.as_view(), name='user-detail'), ] From fb0058042e6c6dc259a557db2a583ac775cc08a5 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Sat, 12 May 2018 19:13:00 -0300 Subject: [PATCH 04/32] [ADD] Working in progress Co-authored-by: Pedro daniel --- TropicalHazards_BI/settings.py | 3 ++- projects/filters.py | 10 ++++++++++ projects/views.py | 7 ++++--- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 projects/filters.py diff --git a/TropicalHazards_BI/settings.py b/TropicalHazards_BI/settings.py index 874d384..3b1c7bc 100644 --- a/TropicalHazards_BI/settings.py +++ b/TropicalHazards_BI/settings.py @@ -47,7 +47,8 @@ 'projects', 'rest_auth', 'dashboards', - 'tags' + 'tags', + 'django_filters' ] MIDDLEWARE = [ diff --git a/projects/filters.py b/projects/filters.py new file mode 100644 index 0000000..98d4cc6 --- /dev/null +++ b/projects/filters.py @@ -0,0 +1,10 @@ +import django_filters +from .models import Project + + +class ProjectFilter(django_filters.FilterSet): + tag_name = django_filters.CharFilter(name='tags__name') + + class Meta: + model = Project + fields = ['tags'] diff --git a/projects/views.py b/projects/views.py index 3507baa..ca29677 100644 --- a/projects/views.py +++ b/projects/views.py @@ -11,7 +11,8 @@ from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication from rest_framework import generics -from rest_framework import filters +from .filters import ProjectFilter +from django_filters.rest_framework import DjangoFilterBackend @permission_classes((IsAuthenticatedOrReadOnly,)) @@ -20,8 +21,8 @@ class ProjectList(generics.ListAPIView): SessionAuthentication) queryset = Project.objects.all() serializer_class = ProjectSerializer - filter_backends = (filters.SearchFilter, ) - search_fields = ('=tags__name', '=tags__slug') + filter_backends = (DjangoFilterBackend, ) + filter_class = ProjectFilter # def get_queryset(self): # tag_name = self.request.query_params.get('tag_name', None) From 7014dd3056e59babde46bead743c276438f60dda Mon Sep 17 00:00:00 2001 From: andrebargas Date: Sat, 12 May 2018 20:47:00 -0300 Subject: [PATCH 05/32] [ADD] adding seach projects by tag name Co-authored-by: pedrodaniel.unb@gmail.com --- projects/filters.py | 4 ++-- projects/views.py | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/projects/filters.py b/projects/filters.py index 98d4cc6..0a40435 100644 --- a/projects/filters.py +++ b/projects/filters.py @@ -3,8 +3,8 @@ class ProjectFilter(django_filters.FilterSet): - tag_name = django_filters.CharFilter(name='tags__name') + tag_name = django_filters.CharFilter(lookup_expr='icontains') class Meta: model = Project - fields = ['tags'] + fields = ['tags__name'] diff --git a/projects/views.py b/projects/views.py index ca29677..4f34e98 100644 --- a/projects/views.py +++ b/projects/views.py @@ -11,6 +11,7 @@ from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication from rest_framework import generics +from rest_framework import filters from .filters import ProjectFilter from django_filters.rest_framework import DjangoFilterBackend @@ -21,18 +22,16 @@ class ProjectList(generics.ListAPIView): SessionAuthentication) queryset = Project.objects.all() serializer_class = ProjectSerializer - filter_backends = (DjangoFilterBackend, ) + filter_backends = (DjangoFilterBackend, filters.SearchFilter) filter_class = ProjectFilter - # def get_queryset(self): - # tag_name = self.request.query_params.get('tag_name', None) - # queryset = Project.objects.all() - # if tag_name is not None: - # queryset = queryset.filter(tags__name=tag_name) - # return queryset - def get(self, request, format=None): - projects = Project.objects.all() + tag_name = self.request.query_params.get('tag_name', None) + print(tag_name) + if tag_name is None: + projects = Project.objects.all() + else: + projects = Project.objects.filter(tags__name=tag_name) serializer = ProjectSerializer(projects, many=True) return Response(serializer.data) From b4ec5336a20c7bc3791e057606ecfa7564a777e7 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Sat, 12 May 2018 21:17:37 -0300 Subject: [PATCH 06/32] [FIX] Fixxinig travis build errors Co-authored-by: PedroDaniel --- projects/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/tests.py b/projects/tests.py index 70a1efc..a28d4f9 100644 --- a/projects/tests.py +++ b/projects/tests.py @@ -1,7 +1,6 @@ import pytest from django.shortcuts import reverse from .models import Project -from tags.models import Tag from django.contrib.auth.models import User from model_mommy import mommy import json From ba4d3bcce575eda4d239b77e0a93fb1312c53c92 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Sun, 13 May 2018 15:41:13 -0300 Subject: [PATCH 07/32] [ADD] Working in progress Co-authored-by: joao Pedro Co-authored-by: pedro daniel --- import_data/views.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 59da713..2caba95 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -10,6 +10,7 @@ from rest_framework import permissions from import_data.serializers import ImportDataSerializer from rest_framework import status +from .models import ImportData @permission_classes((permissions.AllowAny,)) @@ -17,6 +18,11 @@ class FileUploadView(APIView): parser_classes = (MultiPartParser, FormParser) # authentication_classes = (JSONWebTokenAuthentication, ) + def get_object(self, pk): + try: + return ImportData.objects.get(project=pk) + except ImportData.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) def post(self, request, format=None): file_obj = request.data['file'] @@ -37,12 +43,20 @@ def post(self, request, format=None): mongo_client = pymongo.MongoClient('mongo', 27017) mongo_db = mongo_client['main_db'] - collection = mongo_db['collection_' + project_id] - - collection.insert(json_data) + collection = mongo_db['collection_' + project_id] + a = collection.insert(json_data) + print(a) serializer.save() os.remove(file_path) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def get(self, request, pk, format=None): + import_data = self.get_object(self, pk) + mongo_client = pymongo.MongoClient('mongo', 27017) + mongo_db = mongo_client['main_db'] + collection = mongo_db['collection_' + import_data.project.id] + + From 6c5b8b5ebfb1fd269e48fb4a04a2e65b1307045b Mon Sep 17 00:00:00 2001 From: andrebargas Date: Mon, 14 May 2018 15:53:06 -0300 Subject: [PATCH 08/32] [ADD] adding method GET to import data Co-authored-by: Joao Pedro Co-authored-by: Pedro Daniel --- import_data/urls.py | 4 +++- import_data/views.py | 33 +++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/import_data/urls.py b/import_data/urls.py index 225fd25..3fa6863 100644 --- a/import_data/urls.py +++ b/import_data/urls.py @@ -5,5 +5,7 @@ app_name = 'import_data' urlpatterns = [ - path('', views.FileUploadView.as_view(), name='import_data') + path('', views.FileUploadView.as_view(), name='import_data'), + path('/', views.FileUploadViewDetail.as_view(), + name='import_data_detail'), ] diff --git a/import_data/views.py b/import_data/views.py index 2caba95..513a85c 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -1,6 +1,8 @@ import pandas import pymongo +from bson import json_util import json +# from flask import jsonify import os from django.core.files.storage import default_storage from rest_framework.views import APIView @@ -18,12 +20,7 @@ class FileUploadView(APIView): parser_classes = (MultiPartParser, FormParser) # authentication_classes = (JSONWebTokenAuthentication, ) - def get_object(self, pk): - try: - return ImportData.objects.get(project=pk) - except ImportData.DoesNotExist: - return Response(status=status.HTTP_404_NOT_FOUND) - + def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] @@ -44,8 +41,9 @@ def post(self, request, format=None): mongo_client = pymongo.MongoClient('mongo', 27017) mongo_db = mongo_client['main_db'] collection = mongo_db['collection_' + project_id] - a = collection.insert(json_data) - print(a) + collection.insert(json_data) + data = mongo_db.collection.find() + print(data) serializer.save() os.remove(file_path) @@ -53,10 +51,21 @@ def post(self, request, format=None): status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@permission_classes((permissions.AllowAny,)) +class FileUploadViewDetail(APIView): + def get(self, request, pk, format=None): - import_data = self.get_object(self, pk) + + # import_data = ImportData.objects.get(project=pk) mongo_client = pymongo.MongoClient('mongo', 27017) mongo_db = mongo_client['main_db'] - collection = mongo_db['collection_' + import_data.project.id] - - + collection = mongo_db['collection_1'] + elements = collection.find() + json_docs = [] + for doc in elements: + json_doc = json.dumps(doc, default=json_util.default) + json_docs.append(json_doc) + # # # data = jsonify(data=elements) + # data = dumps(collection.find(), json_options=DEFAULT_JSON_OPTIONS) + return Response(json_docs) From 74571b8a35221fd620b6788e3125de9b1927410b Mon Sep 17 00:00:00 2001 From: andrebargas Date: Mon, 14 May 2018 17:50:40 -0300 Subject: [PATCH 09/32] [FIX] Fixing validation and implementation of get import data --- import_data/views.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 513a85c..138b3f8 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -12,7 +12,6 @@ from rest_framework import permissions from import_data.serializers import ImportDataSerializer from rest_framework import status -from .models import ImportData @permission_classes((permissions.AllowAny,)) @@ -56,16 +55,15 @@ def post(self, request, format=None): class FileUploadViewDetail(APIView): def get(self, request, pk, format=None): - - # import_data = ImportData.objects.get(project=pk) mongo_client = pymongo.MongoClient('mongo', 27017) mongo_db = mongo_client['main_db'] - collection = mongo_db['collection_1'] - elements = collection.find() - json_docs = [] - for doc in elements: - json_doc = json.dumps(doc, default=json_util.default) - json_docs.append(json_doc) - # # # data = jsonify(data=elements) - # data = dumps(collection.find(), json_options=DEFAULT_JSON_OPTIONS) - return Response(json_docs) + collection = mongo_db['collection_' + str(pk)] + if collection.count() == 0: + return Response(status=status.HTTP_404_NOT_FOUND) + else: + elements = collection.find() + json_docs = [] + for doc in elements: + json_doc = json.dumps(doc, default=json_util.default) + json_docs.append(json_doc) + return Response(json_docs) From ef2929cb8f4e295d1144ddb2250999c91325d5e3 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Mon, 14 May 2018 19:26:18 -0300 Subject: [PATCH 10/32] [ADD] Adding remove field from collection fuction Co-authored-by: Joao Pedro Co-authored-by: Pedro Daniel --- import_data/views.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/import_data/views.py b/import_data/views.py index 138b3f8..4109877 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -67,3 +67,13 @@ def get(self, request, pk, format=None): json_doc = json.dumps(doc, default=json_util.default) json_docs.append(json_doc) return Response(json_docs) + + def put(self, request, pk, format=None): + remove_field = request.data['remove_field'] + mongo_client = pymongo.MongoClient('mongo', 27017) + mongo_db = mongo_client['main_db'] + collection = mongo_db['collection_' + str(pk)] + if collection.count() == 0: + return Response(status=status.HTTP_404_NOT_FOUND) + else: + collection.update({}, {'$unset': {remove_field: 1}}, multi=True) From 18fc2f5c9dbd714b0ad1f52fac74694befaabcd7 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Mon, 14 May 2018 20:32:14 -0300 Subject: [PATCH 11/32] [UPDATE] Update response format from GET method in import_gata Co-authored-by: Joao Pedro --- import_data/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 4109877..8d25fe9 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -2,6 +2,7 @@ import pymongo from bson import json_util import json +# import bson # from flask import jsonify import os from django.core.files.storage import default_storage @@ -64,9 +65,11 @@ def get(self, request, pk, format=None): elements = collection.find() json_docs = [] for doc in elements: - json_doc = json.dumps(doc, default=json_util.default) + json_doc = json.dumps(doc, default=json_util.default, + ensure_ascii=False, sort_keys=True, + indent=4).encode('utf8') json_docs.append(json_doc) - return Response(json_docs) + return Response(json_docs, status=status.HTTP_200_OK) def put(self, request, pk, format=None): remove_field = request.data['remove_field'] From 445fadb985cc61ba3d002ee9027cca895af5214b Mon Sep 17 00:00:00 2001 From: andrebargas Date: Mon, 14 May 2018 20:39:34 -0300 Subject: [PATCH 12/32] [UPDATE] minor changes Co-authored-by: Joao Pedro --- import_data/views.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 8d25fe9..61a1018 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -2,8 +2,6 @@ import pymongo from bson import json_util import json -# import bson -# from flask import jsonify import os from django.core.files.storage import default_storage from rest_framework.views import APIView @@ -20,7 +18,7 @@ class FileUploadView(APIView): parser_classes = (MultiPartParser, FormParser) # authentication_classes = (JSONWebTokenAuthentication, ) - + def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] @@ -40,10 +38,8 @@ def post(self, request, format=None): mongo_client = pymongo.MongoClient('mongo', 27017) mongo_db = mongo_client['main_db'] - collection = mongo_db['collection_' + project_id] + collection = mongo_db['collection_' + project_id] collection.insert(json_data) - data = mongo_db.collection.find() - print(data) serializer.save() os.remove(file_path) From a774bd670fd39cf9191cafab63d1f252fe335163 Mon Sep 17 00:00:00 2001 From: Andre Filho Date: Tue, 15 May 2018 09:32:43 -0300 Subject: [PATCH 13/32] [DOC] makes initial file Co-authored-by: matheusbsilva --- .../project_artefacts/usability_test/index.md | 92 +++++++++++++++++++ package.json | 3 +- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 docs/project_artefacts/usability_test/index.md diff --git a/docs/project_artefacts/usability_test/index.md b/docs/project_artefacts/usability_test/index.md new file mode 100644 index 0000000..e54b25f --- /dev/null +++ b/docs/project_artefacts/usability_test/index.md @@ -0,0 +1,92 @@ +# Testes de Usabilidade +## Introdução +Um teste de usabilidade tem como principal objetivo observar e avaliar o uso da +aplicação por parte dos usuários reais, visando encontrar problemas e pontos de +melhorias. + +## Tópicos de Análise +Para os testes de usabilidade que serão usados no projeto, serão avaliados os seguintes tópicos: + +
    +
  • + Simplicidade da interface: A facilidade de entendimento da interface por meio do usuário. +
  • +
  • + Desempenho do usuário: tempo e passos necessários para a completude de tarefas. +
  • +
  • + Precisão no uso: Quantidade de erros cometidos pelo usuário na realização da tarefa. +
  • +
  • + Resposta emocional: Como a pessoa se sentiu durante a atividade. +
  • +
  • + Lembrança e reconhecimento: Reconhecimento da aplicação após certo período de tempo. +
  • +
+ +## Avaliação + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TópicoMedidaFórmula
Simplicidade da Interface
Desempenho do usuário
Precisão de Uso
Resposta emocional
Lembrança e reconhecimento
+ +## Testes Utilizados +Aqui estão definidos os testes de usabilidade que serão aplicados pela equipe. +É importante resaltar que existem testes que cobrem vários dos tópicos de análise. + +### Teste 01 - Teste de Workflow + +#### Objetivo +Avaliar a implementação da interface do sistema ao ser utilizada pelo usuário. + +#### Roteiro do Teste + +--- + +### Teste 02 - Teste de Workflow (usuário alterado) + +#### Objetivo + +#### Justificativa +Esse teste foi idealizado baseado na ideia de Richard Littauer, o artigo do mesmo se encontra nas referências do documento. + +#### Roteiro do Teste + +--- + +## Referências + +- The User Has Sobered Up - Littauer, Richard https://medium.com/@richlitt/the-user-has-sobered-up-df0b411997ea diff --git a/package.json b/package.json index 5247e86..ad6a9d1 100644 --- a/package.json +++ b/package.json @@ -4,5 +4,6 @@ }, "devDependencies": { "release-it": "^4.2.0" - } + }, + "version": "1.3.0" } From 5bee6eb1312d619714cc773845d3d994c9e1dfdf Mon Sep 17 00:00:00 2001 From: andrebargas Date: Tue, 15 May 2018 11:02:48 -0300 Subject: [PATCH 14/32] [FIX] fixing formating for data import get method Co-authored-by: Yoshida --- import_data/views.py | 9 +++++---- requirements.txt | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 61a1018..9db825d 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -1,6 +1,7 @@ import pandas import pymongo -from bson import json_util +# from flask import jsonify, json +# from bson import json_util import json import os from django.core.files.storage import default_storage @@ -61,9 +62,9 @@ def get(self, request, pk, format=None): elements = collection.find() json_docs = [] for doc in elements: - json_doc = json.dumps(doc, default=json_util.default, - ensure_ascii=False, sort_keys=True, - indent=4).encode('utf8') + print(type(doc)) + # json_doc = json.dumps(doc, default=json_util.default) + json_doc = str(doc) json_docs.append(json_doc) return Response(json_docs, status=status.HTTP_200_OK) diff --git a/requirements.txt b/requirements.txt index 3bcf96a..d288fe5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,4 @@ ipdb django-environ pymongo pandas +flask From 8efc71a0cb893e37056683480951516c8852bbd3 Mon Sep 17 00:00:00 2001 From: Andre Filho Date: Tue, 15 May 2018 16:33:09 -0300 Subject: [PATCH 15/32] [DOC] finishes first part of document lacks the table with measuments --- .../project_artefacts/usability_test/index.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/project_artefacts/usability_test/index.md b/docs/project_artefacts/usability_test/index.md index e54b25f..bd1e228 100644 --- a/docs/project_artefacts/usability_test/index.md +++ b/docs/project_artefacts/usability_test/index.md @@ -74,17 +74,40 @@ Avaliar a implementação da interface do sistema ao ser utilizada pelo usuário #### Roteiro do Teste +1. O usuário estando na página principal, pesquisa pelo dashboard x. +2. Após receber o resultado da pesquisa, o usuário entra no dashboard escolhido. +3. O usuário confere os dados e baixa os dados do dashboard. +4. O usuário volta para a página principal. +5. O usuário realiza cadastro. +6. O usuário cria um projeto. +7. O usuário faz upload de um set de dados. +8. O usuário cria um dashboard. +9. O usuário entra no dashboard. +11. O usuário baixa os dados do dashboard. + --- ### Teste 02 - Teste de Workflow (usuário alterado) #### Objetivo +Avaliar a navegação da interface, considerando que os usuários estarão sob condições adversas durante o teste. #### Justificativa Esse teste foi idealizado baseado na ideia de Richard Littauer, o artigo do mesmo se encontra nas referências do documento. #### Roteiro do Teste +1. O usuário estando na página principal, pesquisa pelo dashboard x. +2. Após receber o resultado da pesquisa, o usuário entra no dashboard escolhido. +3. O usuário confere os dados e baixa os dados do dashboard. +4. O usuário volta para a página principal. +5. O usuário realiza cadastro. +6. O usuário cria um projeto. +7. O usuário faz upload de um set de dados. +8. O usuário cria um dashboard. +9. O usuário entra no dashboard. +11. O usuário baixa os dados do dashboard. + --- ## Referências From d737d808c085d69e11c4c4ecdd38f2d05276eb85 Mon Sep 17 00:00:00 2001 From: jppgomes Date: Tue, 15 May 2018 21:35:25 -0300 Subject: [PATCH 16/32] [UPDATE] transform str to json again to get on front Co-authored-by: yoshida --- import_data/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 9db825d..ab9630f 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -59,14 +59,15 @@ def get(self, request, pk, format=None): if collection.count() == 0: return Response(status=status.HTTP_404_NOT_FOUND) else: - elements = collection.find() + elements = collection.find({}) json_docs = [] for doc in elements: print(type(doc)) # json_doc = json.dumps(doc, default=json_util.default) json_doc = str(doc) json_docs.append(json_doc) - return Response(json_docs, status=status.HTTP_200_OK) + json_data = json.dumps(json_docs, ensure_ascii=False) + return Response(json_data, status=status.HTTP_200_OK) def put(self, request, pk, format=None): remove_field = request.data['remove_field'] From e6f02fc194ac3e0c64aa1a108b093093afba1a99 Mon Sep 17 00:00:00 2001 From: Yoshida-Eduardo Date: Tue, 15 May 2018 23:21:02 -0300 Subject: [PATCH 17/32] [FIX] fixed method get from import data The method returned a string instead of a JSON object It's working properly after the fix Co-authored-by: Joao Pedro --- import_data/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index ab9630f..0331f00 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -63,11 +63,11 @@ def get(self, request, pk, format=None): json_docs = [] for doc in elements: print(type(doc)) - # json_doc = json.dumps(doc, default=json_util.default) - json_doc = str(doc) - json_docs.append(json_doc) - json_data = json.dumps(json_docs, ensure_ascii=False) - return Response(json_data, status=status.HTTP_200_OK) + # Remove o campo _id do elemento, pois ele não é serializável + del(doc['_id']) + json_docs.append(doc) + # O campo json_docs já está no formato JSON + return Response(json_docs, status=status.HTTP_200_OK) def put(self, request, pk, format=None): remove_field = request.data['remove_field'] From 159fb2734f22424fcf9b7b14823f769366c7d79d Mon Sep 17 00:00:00 2001 From: Pedro Daniel Date: Wed, 16 May 2018 09:39:23 -0300 Subject: [PATCH 18/32] [FIX] Fixing urls in tests.py --- projects/tests.py | 10 +++++----- users/tests.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/projects/tests.py b/projects/tests.py index a28d4f9..8f498fd 100644 --- a/projects/tests.py +++ b/projects/tests.py @@ -80,7 +80,7 @@ def test_get_project_detail_return_200(client): user.save() client.login(username='username', password='password') project = mommy.make('Project', user=user) - url = reverse('projects:projects-detail', kwargs={'pk': project.id}) + url = reverse('projects:project-detail', kwargs={'pk': project.id}) response = client.get(url) assert response.status_code == 200 @@ -94,7 +94,7 @@ def test_get_project_detail_return_404(client): user.save() client.login(username='username', password='password') - url = reverse('projects:projects-detail', kwargs={'pk': 1}) + url = reverse('projects:project-detail', kwargs={'pk': 1}) response = client.get(url) assert response.status_code == 404 @@ -107,7 +107,7 @@ def test_put_project_detail_return_200(client): user.save() client.login(username='username', password='password') project = mommy.make('Project', user=user) - url = reverse('projects:projects-detail', kwargs={'pk': project.id}) + url = reverse('projects:project-detail', kwargs={'pk': project.id}) data = {'name': "nameproject", 'description': "description", 'user': user.id} json_data = json.dumps(data) @@ -122,7 +122,7 @@ def test_put_project_detail_return400(client): user.save() client.login(username='username', password='password') project = mommy.make('Project', user=user) - url = reverse('projects:projects-detail', kwargs={'pk': project.id}) + url = reverse('projects:project-detail', kwargs={'pk': project.id}) data = {'name': "", 'description': "description", 'user': user.id} json_data = json.dumps(data) response = client.put(url, data=json_data, content_type='application/json') @@ -137,7 +137,7 @@ def test_delete_project_detail_return_204(client): user.save() client.login(username='username', password='password') project = mommy.make('Project', user=user) - url = reverse('projects:projects-detail', kwargs={'pk': project.id}) + url = reverse('projects:project-detail', kwargs={'pk': project.id}) response = client.delete(url, content_type='application/json') assert response.status_code == 204 diff --git a/users/tests.py b/users/tests.py index e7c7001..07701ab 100644 --- a/users/tests.py +++ b/users/tests.py @@ -59,7 +59,7 @@ def test_get_user_detail_return_200(client): user = User.objects.create(username='username', email='email', password='password') - url = reverse('users:users-detail', kwargs={'pk': user.id}) + url = reverse('users:user-detail', kwargs={'pk': user.id}) response = client.get(url) assert response.status_code == 200 @@ -67,7 +67,7 @@ def test_get_user_detail_return_200(client): def test_get_user_detail_return_404(client): - url = reverse('users:users-detail', kwargs={'pk': 1}) + url = reverse('users:user-detail', kwargs={'pk': 1}) response = client.get(url) assert response.status_code == 404 @@ -77,7 +77,7 @@ def test_put_user_detail_return_200(client): user = User.objects.create(username='username', email='email') user.set_password('password') user.save() - url = reverse('users:users-detail', kwargs={'pk': user.id}) + url = reverse('users:user-detail', kwargs={'pk': user.id}) data = {'username': "username", 'email': "email@email.com", 'password': "password"} json_data = json.dumps(data) @@ -90,7 +90,7 @@ def test_put_user_detail_return_400(client): user = User.objects.create(username='username', email='email') user.set_password('password') user.save() - url = reverse('users:users-detail', kwargs={'pk': user.id}) + url = reverse('users:user-detail', kwargs={'pk': user.id}) data = {'username': "", 'email': "email_edit@email.com", 'password': "password_edit"} json_data = json.dumps(data) @@ -104,7 +104,7 @@ def test_delete_user_detail_return_204(client): user = User.objects.create(username='username', email='email') user.set_password('password') user.save() - url = reverse('users:users-detail', kwargs={'pk': user.id}) + url = reverse('users:user-detail', kwargs={'pk': user.id}) client.login(username='username', password='password') response = client.delete(url, content_type='application/json') From e478751fb868bf6182c92c4e36f4d66ed90fd332 Mon Sep 17 00:00:00 2001 From: jppgomes Date: Wed, 16 May 2018 22:38:11 -0300 Subject: [PATCH 19/32] [UPDATE] work in progress, filter columns in csv file Co-authored-by: YOSHIDA --- import_data/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 0331f00..764707e 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -23,6 +23,7 @@ class FileUploadView(APIView): def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] + list_fields = request.data['headers'] serializer = ImportDataSerializer(data=request.data) if serializer.is_valid(): file_path = '/code/tmp/' + file_obj.name @@ -34,9 +35,10 @@ def post(self, request, format=None): for chunk in file_obj.chunks(): dest.write(chunk) - dataframe = pandas.read_csv(file_path) + dataframe = pandas.read_csv(file_path, header=0) + dataframe = dataframe.drop([list_fields], axis=1) json_data = json.loads(dataframe.to_json(orient="records")) - + mongo_client = pymongo.MongoClient('mongo', 27017) mongo_db = mongo_client['main_db'] collection = mongo_db['collection_' + project_id] From d489969fd4e8b7ffd02d300079cc1b8181288d71 Mon Sep 17 00:00:00 2001 From: Yoshida-Eduardo Date: Wed, 16 May 2018 23:51:49 -0300 Subject: [PATCH 20/32] [FIX] fixed method post from import data It now receives a list of parameters that won't be saved in mongo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joao Pedro Co-authored-by: André Bargas Co-authored-by: Pedro Daniel --- import_data/views.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 764707e..86b9990 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -23,7 +23,9 @@ class FileUploadView(APIView): def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] - list_fields = request.data['headers'] + print(request.data) + list_fields = request.POST.getlist('headers') + print(list_fields) serializer = ImportDataSerializer(data=request.data) if serializer.is_valid(): file_path = '/code/tmp/' + file_obj.name @@ -36,9 +38,11 @@ def post(self, request, format=None): dest.write(chunk) dataframe = pandas.read_csv(file_path, header=0) - dataframe = dataframe.drop([list_fields], axis=1) + for field in list_fields: + print(field) + dataframe = dataframe.drop(field, axis=1) json_data = json.loads(dataframe.to_json(orient="records")) - + mongo_client = pymongo.MongoClient('mongo', 27017) mongo_db = mongo_client['main_db'] collection = mongo_db['collection_' + project_id] From 16ef2ddea71f544f79a9be05c6b2ed35205be130 Mon Sep 17 00:00:00 2001 From: andrebargas Date: Thu, 17 May 2018 09:36:27 -0300 Subject: [PATCH 21/32] [DOC] Adding diagram of UX in images folder --- docs/images/Diagrams/Diagrama_de_UX.png | Bin 0 -> 95417 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/Diagrams/Diagrama_de_UX.png diff --git a/docs/images/Diagrams/Diagrama_de_UX.png b/docs/images/Diagrams/Diagrama_de_UX.png new file mode 100644 index 0000000000000000000000000000000000000000..292676a3d1d405d2c6f08b31fd17999e3115490d GIT binary patch literal 95417 zcmeFZc{tST|35B-YGi3M_O;~@;n)fzDoUj|m9mVCrR+;&8$)D?kgQpT$`&<5WSdd; zC8NUFca3agA7;$V_chcxox^#*uj~7}e%I&veXgJW=o;g7-_QHGFOT~bdgZb<`)=Od z3=9nH=XK8LGcd3PGBB{nv9SQZIa8CB&%ofvaQ@8cKRkM-`}MLVzTbq*-IvX6`N)|m zf_eB&^PqDSmk8S~fkPj~hXOS?X=WTua=Y%N!7f^GGJZXy^R_7@K%Dt3mz*MVZLMD! z&+lPejG_%=)(0VH-tH=T?~Z?t@O*>+u1cJF<@##267JBEgJl&gBb2(mnrwgVTR!N8 zp`c&y0BHIDfBxqa_g)i@jhPpTWf>L zK#Eg@7P1^ta*7GB_=i?6B*7^>hm0(zr0gQr7%)LbSC^Ojr}Ws3r8?KbDwR|>{Ck;pC&v=X6*aYdN(@Z3e+EC0I;z$`LwmRjFK=-aeOX$C zWppR!B=u7f{my282*qg^hn}eIdMgYgc&3|x_6HWu!r@&b58qOfHFSi;zhmbIB(Tm> z;Z7<3QK$Ar77!?8G{U`+{Jgbd;bS;aK%P?{;agJXI?JR%w+{ZWpNeh9MUqg@>{8M} zraDAodz?EfQ9>1n+~oRp4IRi=ZI+9sNT&z|orTsa=hmJ`>y&KW!RbALH7i7In~7&Q zu;D&JDq@081@OO=%}kcU_i(!u`&(w4NWY!r$x&;x%85N@ybrkm1}%m)I1YVeYn7j#}%|T zVpcLXX6nn{b^bS_x2AK|hprNylv!Ld8JLhGN-;1P$NqJ=Z_@X z+y8`V&6P=nqo+1eWA>7AeHoT4hVSaBJ@Mg+;rQhe)fJ{?U`|BH?JuU0PP@JRzUlbV**Q+xA(gV3sPpjfIy zb#u|nLU-pXs=H^UPz<6i$U30w5L+U5S++<7>kZX>2X2ZQ6nR&Rfx*#cA{&A{* z%I$k>c|UQWyK?0^(JRGmEKg1SC<3L1F1iRm>fEiNgCX?MQdU-{xrrRfunDH!@i*nZ zUAZ&zU9CR*jcsE>FYB8@%9AM~Ndr%Ls4M6V^&4})Kb^(m`vxnpT8d=%iSv%H5M*FD z@sKXq)7}{|D?mzMc9rC(4W+!5)O$Bm;3Tq9@>lz2gf0~2?$pdo9sSDM zD%*cIm`TKdimOi1<%iiN0am!w&0&Hev#!jinSd-?)K=2LXJb9mHrgt z6)y&cs{Jg6u{*98-MDZU1#tNwK9533^9F@Ox+uKzkiKVYsXT!~&+LagXUXs@7K64W ztOKX+9}dR5<73kOnv-E(@F7))c--D=7&sz4t86B?b$%tBK*%lMhZ>x5p$Nth2OJ=& z??&I&qv1ndgOdY9L=%CyGhhK9b2_se=b;81Qiv(9i9xbQGNb4Q-8(WcB=h{Q_)WrD zP(HOEL+P$YtVb#d`hMp_dao+c)*mn8NGP1~JopXi<^XlL1dIA=NsJ|gP1*Pzn2FWz zk=*3M<=)5a`R6o-DOfaNeN~7urrnB(7}UNaf`MX8U2Co1Pg}2jOM?2YrWz4^@1;nW zqkIOV85KVEKK3$p*0{LuImk8DZmoBLPC1AV7=XS9cgw9@lgTEOO_5;6ZyO-MTnN5!tW{ zEu7|l2LP*~>ni;x^CXGEw#9`jJ)#WMbO^nWqm8QPJ_Z*S#~gyzlPBF}7tdDE@QC5k zMM6DlK9%?P9C8Hsyi1anZcj~d0hZ0O=AwXs6)wcye9$>{K2;f0pwWPa)s(IvRn)D~ zsYnQ>_2zYRL;<|DLO(vWab^cgtL}u=Gi2t^S%Ov!9%-47hBSF?ToOc zZglSv;@UbDgH&lP<%n<3xG%>RO@1XGHs zZnp2%eG}E-&BsYtMFmc|@n<|{mQ%m-)T+Zj%J7y1ai6@&8D;U}=BnCjj3Na-x^{y& zv;=IS(g5&55uceghw6I&vgW)`0O6&-U&Kr|dOK8-zxYQbkHesIusrl!tlp(ta#pRf zJ^S_sF5&;K{+*5h{}W7Ja0J#|?R% za9o_}H@GsaN%olrTc5`-n)~kNC1LSNx&?i)dL&8-(dB1Tkye3q* zix_i#c*N#okszwcQ*fzOko)wbQvaxvz1q?<+#-*=pv>xfD`h_NE3k1BnippyA>)5r zYR?4cZy7QAljn9b7XB5LR(6d3g=0VvH1pl^)#@B8^2X%EIMrYl@@|=o zg=jIDQ7Cr<;D340{E5BkA5<(gOiO7o^vwPnF^hfW7*pPzO{Y-x{8S(dK4ym}um-&h zNEk#ac4dL`pZ_>z*eoY+vH{CjELrbgXt?8a860(E7LoxJ7=CO2Pd6VJ85&R;Twkb; z^j#3cW2BuCEQUb*qsIb`@BbvlD=fepN~*)RLcG_@YA9f{oini;o-RY{pH%sC`g8uL zLl1nBlclW+Ir4*?);~cIqdvVDatWk7j#8`W)|>taCU7#99v6WWl`i|FnJuL3IUPlW zkkQ&S-_W)?y0fci0BC8L*9aMJHEY;o(8SWj*Q6Y;8Q?< z=zcz-Z##;|rv9?kvs6Rkj$w&zZeMp#F=1BR+Au+(DG?Z|MSwC~)dy z4iT`N7a-oD|8bcOPJN3oWVAYZ^b2-k9`Lz7MVDH5qj5(A1PqA8PfD2eF6!hIm@V|C z#)z_3`V+PnJZ2APez1Oeiw@Hllr!e?k6eW_pO9<+e8Er5dNpLmJgKW#TEqZ~e2W1+ zJ(Dd~Ek19{q9@A3O?XTo7DkWBjwFNfKe_{HQY9k;!^%B2_SR!e3r&_zjqPCvf>QP6-GUl`gBA73d@z2sm(6za7w6MuzN zsi6lMe@^;Jl>OgCX22`2iyLs zMMX5qYi7+h(@!X4=~u0wqDCj3g!-QnN|$GYQI+$xd;A}IQ1(FA+N+VY zhj_tDuAGD_9KryucV^<*)2W(4;>j^b{ht|drJa`avMTnt{BKwa}W zPCx6Bfh}xwdce(gJ9p+9^AR~aPRu#8=do295%4i*ve`M)fcNK0QQ65EVjua}ZV8q+ z+(50xV6R|hBKUg8Xbg z&Zi{4R|1YMzvp7i zssDsM{9g_pbil^R+-@Zz;q*(idQq}24PCas8J(udqD-=1ST3z^xS|^9*N)qu95_fNv?E-W8bNv0`%}L^yy0 zo_70AV?x>3MJPGqiM)UKLq%!V@Z|F^+?wC>?=A}8sY#k~2~Fs7q=4Y7F9#hat$9w( zc2!1^^9q58%)l@dxV=)_y{l}py!=LtQ(tQ@S49K-a-xWMNsjK9_QmpPS#tzeRQ7Nb zkk2%;XqpE_Tz?=4e)AooWs2$B+`!P|Fl4mrqBy;lF!cOa*_0MtfaIR35d83fP3a<_ zBx&ihlGgQiSb;<i+fCK7bFS>+4haK90XquNj&w7K67VpA{THcQ}T%SjJRel5BZyCX>P#J=+> zB4Y8Hp~Ut<=r^8Hlj$Q5_Hhj~zy+WA!=yN)>%*Xn73N5Vd(pQei}#eYPxS;t_rik; z7WvArujT$nT`#%8B^Pq@H#v=~@oJ%oGNNr!JD%{`WuvrDh(g0CHPn$xmE5z;;v_ie z(|*9K!)v@rFs#Zs9|5F9aWnQ>I!EOrK2( z=H+`!D(U|7`u2wZvvF{+e~VxEplX-(z)5L&SIu^1+IS}_^u9PsY~)4gXEU-Rty!1p!L0&p?*jC&I~*Hj4|$KX6^RLF(EOp;PX&@>3Yw0+*BV`G*pF^KOzNy_Jv9evbRz<{@p^spaPsZlqF!pJSI(-2oRV)pvuPuz6ZihXdk;fKr*tSn4`$w*q*4T16HQJM~`|Dd_GpC9HIktiFfBP%H<^{Z1Fwm5A zp_!<0ekpU(WIl0g*B!b~@k?*z;XeHk^m}`o4zrp|U4#$R_YIq}MZya~8S4|>O1ceg z){kb$*!}n}r1tu=LR$+z2?%UT^Ef#|tr)9{Jmw#jK2=VzcAw~ii5&c|tbosn2C%8{ z(R;f=Qlv!Xponf6$iCis5D&SvgyXvLxcoReC=Td0i)rX%BD&A)HNhr#yyS?mU^|TZ z>c*@|T5N<7$t}pnwzq14bIYIEsyIXW8;X+)RM(b<=2oHCHCche*9{z3Z1hTe#iUvU zY^?k{>}=rCNf()SnarAd;h6;tXEFV4)~fs2zC(XVVG1eA-2|-p|hG!opa_yk^ zm(pV zOQ5Udch@^EoW2EV3+J592NcOBfCGFfG!WFhR%0aQ!=-n98ajQ}vU9y0s=33DY!49E z(ZV0%m_%MDb%<7TgHs!B9=6dCd%y>7YgiFS4+blfz)CK~fI3?jHo(tzXy}-%WVG^r zkztj`%UiO+CgeM!bOm9Tg~f^8NT^V9qn}Q{!-Ut0NZhf)ci{r%97tgD;J1~sV@yRV zA-KKkSU3Nm=4+rB>F;F&OUt=T1Gr@jRj;y+QD z=eTA}AurAbNt>@lufqRuFt8qJ7{6qWoF7;}N&G4UemaPSzk4Ead(ygT@ip>TYpHK0 zRdqqFz$EDWH)hSrK~^ntUUTV~`+TB{XA1S?lE@Q~;^P!-J8Tb~%zN@xul}mcbqSo( zmR3>4eE0I09y5yIZm2X&lT3`Si^k(PR}<7QL7ZqQLIJ8r0{*c-fJubchU> zdJZ0_JZHYKXp-P{D9YR4*VeL(vAhwD-6<<6dUi-0V!8`3k2FaC-`v2LQ-QiX$)7%y z6?`b=87qHtdV=R=GVjUmL;Jh#Q$~!l0zsL+nK|U%8>M6Z8V!-wJ3@L&RGVZO4;p;b z$8j0v)HijTJBW4_#N4#$xxi>LUsx83{CeyYOW~MCG$M$hibVdWtN)lJ$djupSp=x9 zF{3avl2%~CdUO%-Ao7}kPsE`0(dUn);-hF{s?>OR;Lerf5_0z_leFuOce{mUm)u)c6f&yBFwD3;XhK%zgUPPQi$@gkWMiKfy zhN_+|k@%woyLxB0jCsB?OT?5EqijOo3P>E6sOX|m7IcCHKIoEmGJY&U{)!0-nhf(b zi6@2(mPqG6x&5@vB&E~>kV>G24#h4m9E?^6|1rT@MfQG)VuKd#DtrSt`2HJc42Sa) z(F~%2B;KpUy4FRoSxp-~Iz|gOR^a>+3<+wzJzZvX;@io%YA!ZUxn>W`w)$mGz&F)x z)uRj+%^)i`<=~#<$_ArpyQ2r*~CJKf7H~T zK+y5yU$J>|#p@S^=DP1kMpC9n2VVD%+o{}iQ;gt^cy#6Ly6?oQN}MOYYB2CTV~ zQf>JrcXM{v`DIJvO@9M2u8`93&^ct69GVbcWqzo>-}g&{DQ;O-nbBkO3pT1wlJY_lT2Xx|K@xafhF|(AlwQynt`V zaz=Lq>y1zf?Afg>gvE#*m?KQQI4(AodwUf?+^CuIH=A={L!&J@&ifi5j4O-o*SA*U zx#j+%wY?1+cw&EX*Nu4gz;?^Kq#3y`ZhSWfmFOQ=N^YJRc~^FcNvZiQR@cu@)c9r?8brbohH6PgMQ4TE+b~=*k8exAU7utC&Kxlcf9rE-C zDX!a+vX=)pt~|*tx8?=vBJK!HZz6X1PxGio5=|9<{|v?OtD>4Mmzb8o6Cq7ABZc46 zJk{cJ$j!uW_(in{ofL;+y%mZvEo!C+7Uyu|6$wPETX7Od zxfbR8X5JMj{K@lfDO_0`{2gXj9O8*^$|5K6@)Vd-eoccs!>X zPht;G=A!!0`JdX>R^}ldTB_L_e++`lkj~Q6j7U9 zXBnYBg45D%ZukBzLI~f3)JmabQWM|cmgNf5*#h%My=viV)`tavtjNrMOR6%=%~EgP zrp!`1B_$*q#l!RkY!-5QlxHZyeq`C>ILkyZ%Q#s>M_>tidWxrN7K0>LHqjCy;Z}xe zg9wvH;w z*&bkvHLk(id)oRL(|537=pa#7w3d>5Itmm7nvpSWrf0?7kVy@=piFVIYd984C|`HI z70HD=jJ-X9y*7PhP|4k-{tXcENRk9^oF>q9zx-~C-Tkh7zwgn9)sxlQCaI%`tuA!g z2#;m5)Wx4L`WW3>OS!+i^0GB=zrC&y|71xRf#Z0=0m4jgX2B?^5;bUT2aSKld+Hn7 zGEc7Rk9Y}s-#@N>i+|aDm%qfE3w7^%Io)6PP7I`rOxr`o{E%uNG%Dgpxii-ib zW`apXVT^00waYD}?JRrDQtw$mu~!^c7G^IX4!#2EQN?>QLGUBKi!`n!CKQcvJIAiw{eLuZul`2>)A`)G}NuYXQ%J);OYPI6L9ip#VS z_tqSdb)xVwt2u+yNy^<}W(m%Zw|XS${{x*uK45^sfBh$^NVQQ8Ut+a)0NCwj9K6sC zv{O?~hx_(pExb~(MN6AE8P;iOZXOi1?%SeCZ>maVK>nS`*JRf-IhTNy3FRKPK<`p3jtxf2xc(dFwOkGpv$<#|Bu_U+)J8~3jNOsCibZty^e zgbAsw7MO@ybB9Wpi>h+RHkY+0DwC#S{hxFx4(t<&FdoQ2JKpJxRJDRT3d&ncHlkp? zfQJ-PrI#TNul@_^exjPfD$VBtposO&*vlj_1~83DdB)hA!WQN?C)3wrM^XPO|th zam+b5&NCFOJJLm&hxNki!@IRa+qnlLHFVZ{WFCtFZ11~=*lae&tz4it9Ra+?IbeDh z97~_xEvoDlIAS&FM4)4-4w1iZbf@3_s+&J2OV|R(Z&B`|y6@_nZyP}_?HjLZ#z~56Xu83K&Ib-Nc zk8Uf?zYi0)JI~*zk^fHJ&vE9zQuOCYavSCUUr$C%*)U+|4zGTSXA3hjfay3Ozrt<* z;K2_V12BI6=Op$2!^7qO(aHAz=;rF*(t3i`snf9KFS%IAdJ9f{eol|Jy!e7Z9>@BQ z`SfgC*-3|4I)bd0R}(ofgmrJ!0AjwNTlvH8km4GakiJiJ93Ic&65y-j-6`n*xl7X{i?7KL9L-SGM%Lb=d;X zoOOCWW3?*>r5!bT1mZOH`JxCHKX`Y{7`onyGc@s(`5@5RE3oHFjJ1&B`+Rpnxu<~)4Id~pI82n6v1X`%~TeQmjEgCh6?e!{AT-Wgj z^y0ymS&u1@TgKnY@s9+Bo-upQB5U4D8elt9;J&r9BlvR>|yB==+*c%-%Yo}VW1PB zPdw4O4NJ}Mmp+8@#!W;XxTeKzuP6M{*8~eY${F3;0k2j0hNlCP0>r@6%KPs%-c%#nGrNKE-*)^*?JK{+#TGiJ$?n+>))KXQ5<47PR&yTR$$CWY<>wph5k*V@M2G1- zT5&&HqAMB*_P$A~@x0R^t|2q>%=)<;flE6Qa|lX*VR5!N)9)ElU<1hqB3k|rZ)xei zJmUmwy2uZpZ9Uah0?rGuH707Tbf6_}x*!U~ThKu5dimBN)w}zcM8I|`OTK@#%b#ym z1aknF)pV!aPdmk4Ofo(p)Y6ias4*!ehC9F(`lwsmrB|IZdhpaev|G-z^u@`@ZlJeF z&XG?a`*PA=Q&?O}(d{s|Kj#k|r`i0L0gj9;CwC#D=!t}$4Z{oRjOg@79*)d{Br~0g zIRB7lpcN_W0$KACx&pqHnK5HE4uEMxT~YWHd9N|wU`BxgY{i>35|D$H;btWcTlCeM?xJ3C#7jtK{Xy-ktJt8eD~r7AM3ohi(V68I4WN-%2roIS9XOt$TtL zD?#aYEgite)eDh)gXBh3VM%PBV=B0*E7g4S{3QB zzu4Esn~+tr(_4>voHIK8q56B-Vw05PVjo4g?lpkVy%n9~?BzJ#{lyA6>onh*id^_r zEG&D){st32S3^UZHiCU*ebjaAqKNYVmwqTVMYp?#3Z&OS(;~J;rPj7!ko0%?T{xBz zQ}%=rn8X2P*8N3=gF`2(cK<+U2xtP}t_@U6{G=Y=^ggytw%xOi{si*;_@=VcclWzj|XVbp0&0VhDjO3irAszP>4sUuDFH)W`&WJ zOCRb7ghXPGosL4TSH0FIgpSr&-|08IqvmkE0!0&AG9VrT!V5nxZPsy`R7<8S#VxC| z?{iruI!`t?ZhL$w3C=I>!x3zzXR1U3L{vf&L>{r79FsZ(Ue6v5>ATUoS6}OJDaxw_ zxi&`zu)~Dfp{W>RN<6#Q z1oEf1Vz~0BL_2$sH-U&{^)b8GQb*K!r2%|GH0?8%Em}A2OoGLoB+!kqa52voHlz0O zYcAzf3i#Z2C@L0@!-_}C`?ND?ucHU0wT^hI8xVnx$b$Sga+dMNK6UX9p&Hkv8~5LS z1+ry7u5`fuQsAdshfeY(T{}wksUwbJ%>|Fdd%S`Y@mH%YC?G8uUiSX3JgU#=>dkjE z+4zI_ef43u1vR(0Owh~m(pusR`^nx>4>c`^QV3lW86v0}nwu3=ekZjBfZDmaho{d> zUgVK&Q2G5tg1l)BY95{EqZS_zKJ~4g$Z@T8;B&B~T*znaM=m@vAY;A~JQJwB}G%K0#g-T%r{Amnbb$y6ct@=*9OGTmeBf z5_GMI`)IDc*0emeB{HSHy~(v@v{)`lU#&#-axoNcBp8cN#>e@;FS~@6iR^${Z(xeG zy??P~D$?QPEg1!AP)EdPunzBK0fNMM3*c8 z(p|p6o+GQcO#4^l!83cdwX;s^D zE=>|6J#Gd-6FR71&7cQ2=J8HV)4Xf=Q@v?a>_9shn8-F*z7FbXPN!f~MD9%cj4oR9 zDY!e3dlu3ai!kSc-@K+f2EQYJlkhD@k$KD)veOxp=VpToQu6~O$Vh!eB2#oJ^T#f7 z@Nd?;Cs=RETSV74flp3W+3fudN7Ud(A{Y^E%a2aF12^r%c%uH-zu<@-e)u(p<5Ol#;uH){K3TUT~|uP1FWws`ep% zl^?r*6r22*KC7F?Tc}qWm17yCQ-gxD!KM@DDL#n37wT*ctiCijodvnnd5}D!^u0|v2>5#jGv{AjYMy0K)XA`O zApUPvR#S1;a&#NVP_M~IK)ZOS$Wt^^$o^*`uGt?V`m`Z0w1E+h{p3`^Ogm-VPLyID z0i9A*F-#(7BVTU<3v-)~gP-bFFX<{Q>uQ<+hrBPW4@<`;wE2$FRI_m0stP`c=F z0oMsiX@9x|Y!a}xd9pGl9m_Qsx#P3)<=Io}dYG9Q%DA6R>SEW$!>(_Tbd(DQ!jt_9 zVMPL~$%S04-m5Sji^AI$z^QAI6ss#!z~#MVRiGK4#9&}n8x;4c$^O>O zx4QJkpI_}?aSI5FUzzM}k}@FG&w6q}rz{dg7!Rlf=$Smq34(~KKIv^APaPpDtaD-N zsOAur>IOF;bt`nSxFU{)wJJ2gYu4(VN!^d6%PlUN275U;3+&urL(8vh__&LX@6Npo zWUMM#Y)(sstQ}EWfrnk{!az^jh0if7$#HU%8#NlQ0mTZ6s%k!{;;lpH@yZjV8pOz# zn&ThUrOtH1zNF$Z!ok^O<7TMgb~}6r=30G@7z=|SVV63t4XCNvj$ifTN7f*q#L4X+ znIQ5)T4`T>2nrSivJc!a!<}ls1WX{H(}SwpI#^R~zdlte`3Ao(UpgJXYIb_NMfW<^ zv&c_IsR5!8wc1hd zzdj=YfLNbzaOiIt*)TI5z16XN9KMi6Bduv;9K}R}+3wX4Vc+B7%^$+=C+3mU7 zp#)W@xk=sO8f+e&C4-!)%dXRVGQ#0O9*#hnnnm@UxJ7RyGUQLNa&NY={obuu18E`M zFm7->SRZ3`P%A@;b#d-^-1(4SPF!Dd;81VpiQ0{BLT|0Dg4q*t1e{tUuZKhkV9-mG zi;+7u)mOf-3fVucpAHZyz;?Y6lt;h8D=y2AIgc8G{PpWv+F7JmVpnnAQIGQ!708Xd++te z1v9D1UsK&5j2)M>?wGLdYe>@y;ul?y@_$lh!hHDFE|W2;D9;yGJr~b7P~e$-^E;sl zs`2Y5KMK2*`>sS6fKSRLN0XCCU>o2j&4H)L*9Zj5%}wJP+s1sGZ?HgIiXZ1Ccf8zYdtNbZ$AIpj7&>o3u4zNoD zN=)iW@0k;#5Tl@D<_9CxPdqst$!2bR81OKU(`V!+<|iqKgie)^yd3CMr$AHngj5?f zLg~#+hA^LP%xUy?#uNo(d9qtizYj88L2}iAX+4}IO+Qf06teL zrQ1&Lp*=*6lEF=PEOTO;TR8P2d)*6tYf5q5rzSneSKLYybLa@;k#$=?apGre=BBM* z{nZ-+)e}AUeq+kz9x_*>i&96Z?*mYxJAwh(Rva9RY&+9Z_GAZeeZVeB-uXz-J73Y*&Q7_d3fX{>E~B(u+QA zaH^ng6A0J>>E8iK{+93TM11C;v<0K5aq!YN8V$5{GG35MBoa)gWMN49z|yIJUV;9F zW2IABUvLziNFO;jdQ+`$x3X+)G%TF8&++1)cOn(v9P`j zp?;B3cTe^>jy}{oCOi5L$UzA7QCsB0t9u0seS^A)0CIJ5op@tc-o_zbZ9S+mfNEow z7q#PpYM&7E6+!`U8Mz9sULOJhdXmGc$RtV(EmY_;h9-@UdBc)at$KH=j!f~AvTsLf z)#o8auqrDGmUZ;;8bfg>@}B`Q)POzkOu};@GjaOtE+1TjyLSw=Qcs_Qo%4`y&gjH#nU`3l=(C4 zN_s+mRTy%n)I)tv@`+!%>;iyw{&dEZC3(s-vkkO*?|Y5_Y+$*CUQ6)JQ;b4Tm}a6z#`aK~ehN*2up%2n`OHW`4wHgJgm zqMj~<1n1QAlR^K+)GS`*jcI8ImiP{fy-?9le2if*>67?eprV-Q1p9PwkfndykdiaI( zAy_w$^2p+$9~1RoPT3V+W*iPI9J1}g@{=yIagY7Q)Dc{peKZDs%Fh(U?~<}@Wbo;? zt;T{LZ?}DV-)!-`MuSN_YwGm2TL<3n@em#p__yzsJxh8#c*LjTnwj-x*TOb?d<;Vq ze+Hc73V3s^A;7b_?Ra*106lIl#mA)Wv7MBT*f(t)?a2DJ&-TP@Jgc|5?Y>3FHug=O z+wNG(H}xplv!Fky-aZrFRr|Kxy?2_U4CGV+aok1|K2g(}8S2T4hqtQMzdx2gjX)i9 ztFFCUBIAd_#$vF*b&AUcl`h9)$v2PDQkiL0j+^i>)u?M1f)UL#dxi+E;{?|>(_>5% z7fO+_3xxB^9f+Zg+b;};D;OMl^Tj;P7@VT~5L5m=?9A>q+7*(}to69>^`=D;vstL{ zCiR<3Ce4I3GvrlOn(QD6U7%G{^7^nBJk^<7D0|Lq=(8W|+Y8_491G#jV7~(%`H~tm zt?!8UNIHGkV82p?|E9gnX$4HNqFK;7@IrE=_uU)$#3JIUA; z58;}Bo+){|RkI6$*U*ShwT$neaAeSieXq}2XQ&D>9;WWxbUIZ`H#6?7mUN@vgQ_(9 z?MMBi%B4B}&Vl*uI56pLugbX)(foHFTwLFd+%n*eF~T=Xj1T`^^dGkoy;R$#x&589 z+cJTKnQ_|6gqZD^kg#cKe@Y>C!G|q@bSPxg8E{y{ei>{ZE8ol>ck@Y?4ViI; ziNX?UU96JM27Ca)K!tY#3LYplBzzV=2s*fTIyBGZa6YFz4~rBFD1d3_<)C+rA9#~N zD;^KnW26|tiZ{E=s>_MJv-rtU0zCI}%B(9XS$%n}sv1SUmqNlV=bpSqC>g}f)6HnE zN@#8ai^4!RBR(oD6*|pQwYJ-dmi~L1qxa$YGg_n9B4c=$ik4mFg$6;S=ea-!~_;xNfe* z(0ap<%#pFf!sX*tVRB{T=;A`_Cn|`~lEJV}0Ai4vDawZRs%nL+e(#{yZXw`{ogcvWY?nDsXFTf{3A<(xahk$$;?2 zJ<#=|Yo`Xu0a>m+65nFijBQK{*G#;q-Z&9*!~FE3^3Qb$vzw=?x=Mg>Pc*Eg_P$T} z4qZ?OF7EJSi5{x71r+?d`67DBD`K&{mNq3NL!M9*iiFat2Yq^iUN`*?Sn~!GRI5gQ z(#yY09X;qDg{0NW?2ZOr6IZ`zB7jBA-$%F<2u8{wbyreojWt2M`xNU%K`4WUp=QsY!k)pw{AbxPtZZVFo@BT}@;ZATJ` z?@c|ALz;^%sJ6X@#d}BPT-h7lEz8>Nylr}?)>A0mgGgJQ0V7<7GhFUcXJN7A*Q~m6 zdRegP34E{ruyR!rMVlA_hp(Y|uaf1ath-Zp$GitT@oRppictGn`U4KP%dcF|LXaJp!hDT~ z;|q()!7dK3#&Mpuj*)`8dp>lmm2|#lReGE3<(Qe1R%_==AqQTx@rA1uRtPps^&0dh z5tp6Lro14ZY%i@~)tvM(MiGY%->Q!m<4BgN2QKG~Tf<2%WZ;M7mhp`aLEJE4Mv-LI zO;OKH5QREcJH4lMURWt8Ue6ap9mld~k3u_SUeCLL0$&!mfq8|!N6jg*YkAty71t30 z1GrM^Z-`EYwL5A78~QaBiMN?UX`F7=h54mrg0app!?XtnD8>f(&jwni$0c_}2%1me zTXxSJf0|~*5t{c2(;EBkjoW45MRz?MYAyQs_Gg)`v{LD%?_N=kAf6S90iK9XDYQo| zgG=5GK$~+2-YC&`dUd9`t-AvMkP$2F*-c(m-Mev;1%|NuUO$ZSGXYW;^rhf)A+K`o z2Un1B_~Uq(u*D4Amfa_d_Yd+(oT_c&U7dSC(r`;9wVqeY;(%#`%FB;TZl*b$H7b?b z;rK!T_yLPN2QPc)iJNM^AZ($kL`=Vac_-dQEg=Po>l|*sS2*~=u2<4SZfg96<|Bpv zyd$wVQ`Qn2TjbjI6ulC$vy0*n5P-k;zxf&%rlGKUm5u*x-lHCe(^d%#lqfKVSh``qWQ-IDi}w5>@AL? zmG~(2{`YSRDW`f@ZsA|QNa1)2xA(29?V%^ z=%R{rR?T7!b8bjDN%(`u0RRg*bq&7|U_KJ$Oe&!Q$f1b9KayD=LYO4==q{ zQ@iuT)d4}cB_la{GVgdH$J*Nttp?>kKB-)rdS7IEW}}s>8bujnQB)#i)m^FVSRXJF zutby%L+UnTfuHUmqR;3Wlkvwx6x5sC`8S#em*WRvS|+}!#5WJ+C&}ux-+3blxk0sp z)NBlCRwPFKrADPR=ZFW|DkHsg@Tnjrm8K?$EgUjR(Va{#Ji@7u&k;;(Sj%g@rs6mY zzBLBC{BZd~v?no5%l>QU`dd?&N5)6NZfXx};+xKLB#pe%PJ|1Pu9%O?)u*c3VC?O^ z3C=Gu5M0ir!b`8nCZcUMIUEJtCIsGyxb8olAtWd`eETKkdl2pD5BW;NY0i$Z=Sq0x zgdf8@3=L7s*x}00@{gZ%-09-uug4PLIu9-)ab&XB3md2vuc`Oy3mKG>W@h>o;hK4$ z8HktRVUaA~it$xQ$qh#TsFG8u-g*aB5M!$Gil+T6eiQdhZvet4mq`SDffA;oaJB+A&(JL-nyj=d_#@o5cOXF86EQ=}SpGeh z=D195UP^SVBuG?-waYCKuD{-S$%h_{-Y>Ggjra9P;G<3Y8VH{e4kW^VnD`f>5Yey$ zrmZlWu><9`7Yqr;FAtnZKv`=Y2`A`knJSch8cTMh9rWg10ROC@{0X+7+YTRQkh~a?`z-b&R8?Tz+nPA2P@*eb^Ox=& zmEd$Ciw0`_3=87uju=}xB+RU^;HUE>y4^+Kr;Hp&o{}`$pzaL+|z&? z72;VM*#GKK&}$JGX^yA4Ev!mmc3uD*VeJ+&3I>t`-GMnGtFouYQv#VMi*oy-nrhSU zmAPZ<<()6ix_S4NcjvWfx#>L%Hn3f6tr};2xkXXL?_P&K!7=0MhRDqOhkDBO z)D&1830`hD!SWTwuRC8j_71F>c$fFhef<%KjY(9kW9$=-P&Z79_oqI|ZyY-(!5aMr z*ja@|^nB{Qb+M>2VhhE7*4CB7=drgBki^{Fn38!2lOF+Bs>8H*sw_BV5lv;?nT$jN zya;*PVP11jgEbTe1x0hBYLp3Het)Pzmx9k`l|6ZlvElWu$!3KfxR-O{EC>1z0`EWS?o$|B$9O(p(l2t%*g=^I4gnKBkcGI~D=qsCB8bo2u#i20dF;nr zw@{=*`0xVK4i@5F4!)3RHQniUkSzzZ_Z8=4S2d@_ST7aXqqr=G> z4L>Bdaf@Wge_B#-?_lOxD{(5IVi$l}7Tf(Q~B_9a9>RtW)d01b<>MHT}LLV$ol zNQe+ZzD`ib=Qq#$)?4*fef3p+5B@P#)4A`v`}8@d&$-Uk-EE=Jwy}5K>&}Or*T_Zd zB8pCNL#eY0K_defmR>F2tQ#*tZR8HMRA#J&#CzYJeBwyHqGGOgTVaR_*jG6rJ^FR- z)k6*yvk$+^~93_25tWEw)#G^ErVzEctg5egs>iS%? zM{B6sHqSU=e5+3pMZH_PN36z&vQ~z5xb|)4ABUU$h?iP}{+cM*g^iVtRsFP+udWr^ zI5GIO^!t(%dJ-HD@Mz}KtBYK@(tEAU_B!u=L0kQ%e_%XTFfipE(t;LB3pfu+4kTf% zLirxso?A!9+RUKk@Tbl81Yhb$PF*7VTW?ozE_*=FOe{ZHPf3@L8cq?_G5s(8WQLXJn`r08C_zQ}X4<}R z6oMY8OZwJM37xCE?o&S-osY<~$!Mh8t22TVa$q>LYXTNihVC6gcr!!nNBxO*Ra&DL z7%!`_gdr_93J&7rV*su<%+h^RgkfyTnVrlrdpK+O_A__)H~oT00VXDHHo_8mc(yq< zO3ZDki$DTWc6E0QZGW`>yf`)4r_-a-FArl4;kihSsTN|&vSZZv@%J>Gut?9xOTRQsg zM68ugeIPxXA!GJ?5^3f|Q=wh(G8Y@o(LuPKi@2t#&+tdnF9Yj%WQC)qRsg(hB#wYc z98QhhQssjr(Jz9D9bNg0vKzn#W&SLGiqK0V_*S9mM7uio4(QcSzEkAEP?t{+^CRlN zz!}h)xyFVsc5O49g@Qd$9w|GhZ6BhaJyMaA2{j?loQo2YCv`F{Dd9e{xx%Bb&c+xa zd}QVE9gFT(;dAOp*hOfwvLOXqsc*c@(56T)KJA3dZsz3C09R1|ks4w^?50nScXPI0 zD~Q=|+;Z7Bs;spWO3~|g2%mZu(|W;a&VNt!vj0?76|8DLo#bsCX^W~F7x2`MM2bi1 zgHv2kjg*PH$lYGtQM3EX1-1={{K3>!D|7EMyzyl)urb(FHaFFZY)3vq9(Sp`ESo!^ zgMYT=3+SjoK;H@?^JaH-_i`P{kmGF>fQj~h$35r>R{8H^#yNIE$IP3Pq}vPWdE<&u zH)`2hT0Gbo?;0#YUTpNdWj6$`CTM8pI-|kSY7M(|j}WSDiQ6 z7>MI@lLH2hwaqcUhI%yN5tdwXLJx|h{}hWgExaKXyS;G6Gackb{=OkB?_sh^r+aq7 zy7eN^1URZJ=JRiS=ihw|QFyMp9cwXO`!!R;uG^;jj1FcB>rB~$;g5R!yQR5h=z%F>b9XWWB?+s3?jYqAOAb-bZU`dsR^%?1j)Dm776^F>7Tsup$Z z@6zI+QoE>coZwZv9pFyfzb6KLcgl_wy#XGGU9Z(0TbXOFB}PfBjR?v|uy2?sGFT#E zQqoN6JG`Z;13gomT`sZBeb|vQT8s#nF!9n;npdF`Vzl40A-Z5)f~Bu4zqR5asMVZ9 zw?w_Ct~Je3ZGd=e+Xc%7InqStaPjP#?y_G+gPMp1In47T`U1J1J`R>Y%|tt@AP2f> zY6h?uNCX@HnO9X%K_q;@cY%=Ty|v+1#RCXFaY-S<)Cb8ag|lkQB5GR1cbXB_b$JYPvO8wgjM8U%#VmBV=Zq7@s(9boR8% z8hMc@%Ca?tON_MMK0!=t?OxNowdHWDM=Qj6KthrMZ=uI@+x+3|?VZWI=9%F}k&(1$ zJB8OcJn%wjP_~rbt;&R@b{WEc7OSeH zA{a#tbf-NDP?)Qzp09eQi9(MDEH4}X0(p@xd|&Rr0E8_sB54&C@9&{)B4?*LrX(4W zYP7_W+Y{=nPEXeAaE)Fv)AfG%&1)F8}R)_YvSh!^YOB(=O>=(ZPrF8;0;F5@dR zG@Z$;A_ikIWSy4Is{p6}*cPaln5q8e45i;$mwqSINi=o;8a=Cp`{~~}LC%yx}wFE|@#@=P+ z^)?EnQk5T&$^b`_p6*N+(^Y?UrLwUXDx+K+Fb1N zqGf$;up(cd4beJhjfa<1&Fjz_i_=Q2mrphlC0Y+LCi;FNtdAtae#gQ8qZY8?DFHf7 z{okY0d5;iA>;l0TJ*n4whFHQHFVAOYeC_^Al_WR z;{O{M@eiP!>(^?=&k=C&b*^8F5B~+0=lZo)03P}U&FA{H^6>-N-ayFz+d*-F>i-$k zkJ`ll%+$~F&i|<2j|$oUxlPr60A;^x_PWa}Ti7;=c*788SZq`ne}wKbt7|;}X>SdO zSS@Wdy+NVFlM&mwnp{J_)-`<n-NI+F8xz{GiqT z1FL%NGo+ct@DJ@$jxb{%wosetTitCPqxSR5_t#OA2ZZ@8aF^4X3OMiRN*pE~G87-X z#oM+Yyv1)HcH4(yT!r1(1$O7kf>=&}Y?A68`v_J%uCnJrkRkG>SG+Jz->?kfUmo1T zqN@o<-7@FW<1~tg>($W@KjKbsuPv1^OX$Sa*nu~UU4q$9IVBZ$51w3LvvbGl=`u1y z(4S-IBBajix2MhTy~XCw`F&y}Vg`R{9aS8TNWzsr7-Lgeo5)NwQdp|n?}lABVtwEX zaKg8pzg-^OerNjJt}ynIF=MOX?H^5lyuE=~G=8PH(Zf1|{j}srxY#?cHg#<+_JiBB zHSN!0x(6MkGC8kaBkiH@i5Ucf!&H)Ny2Gn8*u5{G-=myAi@zzp^#JJJf)-uo7ukv~ zZ{MBsX!SZbyWhV^WpbSBeQM;Z< zisfQiGmg^Q&|#!H*jjDayRwNU_B=QSu~aXHHa=wvg7Gq)mjZ7}f(NByd3_gHo4A@t zhx~LWI8!7Y7rS|kwUz63xAoYqR*qumiV5>Kv4pv}P?)B#aU4y$by#}GZI%od7YrUT zABaHuViXZN6p9wI0sJ^(_P_s1#cQO?SQ~ajmgHIderos(V9p zTPGmg-&o+frX^ABEPsv~3%45b{C}cj&8lIzGY4<917q6|tMk{SF)kcD{x~?76*M4nHyik|fH`f^HG25Ip5k?`B!XWCCudWX~)y+jp!4{SOG!`J8g zW%$2yhTks+z6To0jKHXV8SaDsKMq&AihH_decBpTt;g~d+4Tz-B3p_xV-nhCma`Zb-PtRNPXR6x$0#p`IUoyDqf$2X$Wjl!y{(O&pz zMp>96cdFvVKJp)Iwbh>e51p6e==>;>KT5H?Ifm-@71K!X)?4*m9~Op*UT!DzYvn3v zC(o#v=Fel@`n*ZUR#~N5`qlw*-BVF@qgt?1Pta&0U zi4%-NB1?(uUw5;$`Yy+kc_6=Ny*5Ydv1$6o!Z)awjb4k3rnGzUAEVCf@N2(LLm<9g z+K-KqH!h#!i;5$khx+ubc|u#WJw4YaRy(V22qsv5%o%W{sCkC*zopI$5cOgJyW_k0 zyjf}b#pb~Z4Q!sigYT_~f^(shIO3(T@9vqIy_D0FP)(^bLXR!55q)$k+G>%$@fPz| zTFHzjbce{GEw}_{qT{v7M0J*J>(Ei08`$#(-fx8c58M8(hbo}JP&*y3(a$(3)3}ed zUpHC8aC^UV`^Rmqwp=drj4HY-)HB_V{58b~t=~>`E$vj%3!gbEz;*};i2v{=#vE@# z$5O$`>NWCxNko-aCv#;`_61$+Jb<4k``?o`@RXTz03CCO;g{ZPkjiq{E^WNNt5OYUdJ+AztwIhnv~u%kkZAO}Unk;|)q z4uwN0!Ee; za;`>;4qeJZ8i^y9K32w6-8jeR5)vMhJOeE*MgI_54zloQ&G7CG1^pf~4sA?=BOnoQ zrM@>ny!jTynw0FF>76hNBO+@HvyQhqsw~=e7NlNiexP_juNzlML5jF8Bc>{=r6*q6 zji;b}pjF_5Cazg3Hb(6A==FKbe|YzM9PduFll@!;?m$4a7^_GzY19|VS=!Pbb~4%A z=h$4i7b8xx^3ZdWwieAzAyXw68GDL!mIu%Zhf*3cm-3A=^x) z6Z0%I-+VM7IFAh2o^mjRSE4m>PpjdHZFKkW5fl9n|IKm=1BYQ)wUk@tTzoBSLf&%% zl&FE5s%@;3p4TzzH+Rz%v|cym`;)%cs(1fq^OnbXi06B;cKzM6qaClx%q9XDbQ?DZ zJZ)5Atk07vSLvIqr_-3+` z@-(kQXRCol>=XGLas64KC68FYq!Evp==Eu=lcJ$^j)6S%?D^h)FaDH1FZ@0I_C=}Z zg;^RErIR|;+A8TAdvrx(=7w#dX)aK5RbZOxa8UmRYex!Z-T?@oy`p{(xPv!V4(era zmIP<9MnB8xsch4hI^#%b$+W!tXEEaSv>{Hh8|vGUZ+COEK`ZUad?l1^6BCCus?b<^ zB6hgGG)lLX?*1wjGO}0X?~pM%2asq{?sGL?VL)z63nWXv<*aE6a=S9+M#sQYPi&ixyEy)xpH^k4Ew>~)==mY$h2;(VNjBj1czI@`=i_a9>SI^> z4KbW`q9or6UM7s5Gs;?~giuN0%=rwoVd^J>)0!htI_YOY31`N#agJCQYL;Hl(n*TKjJMm)vNtU>uDb1y{@vaw-jZ%vzqx zi=59+koGjbd?k3hhCJ0Aa{s9o=D;84p$|U0^GCW!Yj!Ta(>I>NRZBBzY2ID~j1kw4 zfjY$LB3Bog&&*&mY`9i#!Y?y7_Qw&AGoLDMjV1nR)rH!wLsmKRyn zqTP{U4qo^}9vVGjA}dc0Am0Ga9O{_p`7GeeIdiAr?SY?1h{$&1!+-J!;RDUY#ijEQ zZ!Qgq9E6@YPq;e?I4$+dz~Pmhwqw-zdI^`s4c7yoikBQk9d0My*u|&wnW^$UEAuPN z&;ZwB)xKLfsXbmFA#wxM&!LZ`k8d`xPgF)G6jOHEf0Vd6nK=eWT}v0tSsco$=n|53 z479_14U0%>8DT|!*Ys;WT#m@FVBztI>u?lr<#kmYty1I4BzaBrrthtHa9t8@E#vtq zlv)0s(F+twxY?!w>o7`M)_QSp@H%;F5N@ubB%;=(2gg{!FIMjhf9!}a_K{+#K{4(Q zUPG%U*CUef{iraOoeY!B8O`wxr3W1eU)~)~KVSwACn3WZikw`+m?iKq1eCSE@B`_g zM;pD5(MssWw-HziNAy-#;A?;PHsW#VaKwI_ZBcPc-2|`xw8uUxY#Qf7*M6;K*Zzje zGEJYx-}%OWD>F)SEU-VOh6~HWkpDt=596CR{n@qkOL+759PWrmQ%e?{Xy|MjDTWWE zQzyEMk&MlTJ%!aT51(?h%X-yl=4`L9R5(+Epo)>KoDv#RU6+IR#g+fgmmaEjv9n;S zl>(myzWCZQW3{ir3#(=tDbE8*LvL2CDC1B{r`-92b(wmrfb#iT#AKWE=c*8vy388& zLXzC$;6ADySeTcJ0UHyq#%S2ldTCo!N25&1RPP-H(alA!u9B^wY(_x1KHYqJv%#$> z@`pUaL8nIP1#ndNt}zc?@sjpkSbKoI;{+t>*p zC&>fz59v~&pf&$5TEhd`(BwD-JZhf*YD1PnaP2t$goqTT(tMj||iKt+^ z0DE9r@emg2^6g1r8el(9{6(I;2C4{=rUMU9jy`cis+xl6iK`6`vRywa=+wTZCw-E zqW?MB+^J{b?&M2O$?Ho3P2h6oja2(r!O($Ve1(q8?31&~zPEWFs8sU%#&IVIT?oBq5lfd_(L-YNh*3s6{z=!iu7t!0o~p{%@YvUZnj&nzBO$ zRjxH|pP-+See=`zj_nun3kwbcz1!m=ZXgByuuXH08!>Gl<(d_@bVQ8!BmlT^V?T{LRyUXjJ+$He2QkSJhWMUE$SG`16p1a4Aa zoZNDFv9d$YDe37opY|sLxqa91f!+H6^+^Cl^=Nx4B;HG)JX~s}<3J?pgfW_NnjIh? z`%;Mwh^%I5NjixNSE&U?E3N(ek!N$pVwe0|{+D%~0(G&3I8QJIRO zPlXonKs3A2#jgA2EU=GUh!Yh%z&Uy#-^sC$^_(sP39L)#HSD>{F*k>n)f=}wpbqQ= zW%u_$b0oL!^ro^~VLE5h7Wi%Cr?ZhE5lMEI2EX$oq+F3(45W#qEIe)c{$sC!p&R5L zhPRhZC3$AR@2CxCv&2rpnWeKy`LTN_c?BW>)53knv~Fi;j5&3yNPOa;N#kc_U=&<9 z)1Uczan-Ng!9KBgYoX2IKMe%=4dV=3!>hG7uHbF#*?Tno~MPnx%F+`1l zYX3}Y+KUgdyd^7iY0XqX(?A{6$oz{JijIwDag{7Uc4Nh7si? zK#%FT?=i&K{!ZYB|61bg)dVqg=1hxg&er>hWIbh;$o7>ns6(Q{`=s|RADxXH%?`T= z`j=u4^zPR_-+bTI9q@DBc|nuTM?6~dHj{PHdV}|Vk{WfQ)peiX!x_YM&eXFe{J^@2 zh->in#7v2RsVvwXZJAoumhVv8NG`W9GK{m^3*2<49$M?Y)d0clY@=1V9+L_k4Mz0 za?Lk*VlWMH8nAwjq|?59?QwlJ3;En1*WsLH2Qzx89_sj1`@tL{DcZ9I@lBFwF&`ZEbQ z<-HXRhediw=_}6!K4nVSPVs^(6CYX3UImDE6ISEnkWf=q7iUK@$m|NiviCc6Pk>Vf*;qT5w&fl4CoX&PS^)mk6OBiCUkfEVlx;k&4y#kV%36?v z;G)u{)k!#`=YoSXk-g(*MDu$?OkACbjIxT1+LRZ5kFfX!((t6tuJV`T&ThbYU6g$8)>m^Qf78TBoY@DwXL7DExd#r{;(?^^?_HiD@IX{-2C|%-Q%Q%< z;M`m@y!2ewE1%zoI0Go1>vqo9+dFD!S0+>A57Vr>oehWf6-(ru>tsA7FE}ng)^ERg zbe~kR~FH(s$!%s(V^I|g;p4eI}4F#Uhpc=tfMATpOjg+M=5%H&@ z&(6|1^v3TZDB75{hN*`$v*cN*%OGEr<$L+@ga+SI0T1eeQRp*QuqOya1l7w-0trs8 z`rVRUFam!Gt((q_6_a+e&$!?C*F_z#*u%4uC__loD?=ev2swi~N5=0BzSu9iIt_*Q zQSpI?M|zc>}<2#t+fdWT%Jjn?Toc6tiMb<)%hdYMM2QM~G_iUDM#9uZ$^ zO^ZsoK_+V|dzrZ&^F+DWRdiOxA!o!nN49`c*H&qB9&0FW=`n$|hQVQ1*>Cq?{Y_bS zQ3o$~XI8AC=L>Hft3ulizb72%O(1K1DH)_J^}$5=f(bBtm?X~%0Ur!W%77L%ks`A86Z)1IDaf9txJ7(Su6_f`uDSLg#D{DtG_k*WFltvh98TYO1`N{Qw zx*&g)YeI8UM`iurn0NHhPs`T~m5z`tdMyvPW<`AnYFxvZ+JPU%!bzW*~)PLq`vtq)YjP(ePK>y)WAe%>iz z_N_BTi#nNqh}m&ysW9p6mg8!PgN;K&TkTrjQRmxr)mV+nzWt9`0Y<T6|y$66Fzf(%;7}v(&@GVMetkZtuGo*BD zY>k0L5yIC%mOslSOmzF(?ygTUdC}TCelI#~Vrq9hsVcOpO8855n+OV%fP`9bE;0Z* zdSF9G8%{c#&`zl?*PcHfD{$CS8_+Z5BJSlyD+4fCBd+_)0Z6(M+MN-V)EK22y#h$g< zACY7!tRX-B;^q63C^$+v`z|cxO!U#7hpl3k(_{{v+2d5Y96#gTk1SF7umghwRW~aOt)Yhfm(%`oh zn`p=^6QD|)HCq=>p!VZG3LvNuXEzFlK&W=|(J83TPCeG>>!TyeH8Y;xD7?I#Zn5+h zsfH6UgXokXkh zxn$Rs(@@nEQ((l21I=X=aq3ZRJ_}*oGn%Yz9EcIAalKDK$4= zS(iDdoT+pghIrPE{J!9ryQG4K+ORbfYlkiB>t2P4#ZG~^6b*o0&gKj*F8Z7Ap6RN0 z(fhx&Uz$YAD10^_J)n5LSH9MqZ0mHNdRvuOLSY&N!zMs)y*u(to*NnbK2c92c-3Qv zUsQ)x!JHRzdG=wCJocCA^V9CPdwWsaT`=Ay%ntfM7o6+$ntP_%`S-BG8>N(;R}0OJ zJhevNX*ZY%BXZN?(*S;%rRiB;YTek4*!M=Y3xi-4Lar=dYZ+QqdN_7^%bC>d*rcym zO{sIVi+e?0D}e&!f79~?Lj$3eqs?~&U~D)Ld)cM_>_ZR6!XX-hN7z|dsw!@FW`dm} zrmna*I)g2jD&0;Gq-P7CpW33x`IJ?mvlo1_N+Kqo0=A}n9Db|VWU+)H6bB`%z6=Bx zXhqAY?K%^=gZ~_;8Vr=vBa`(F^YPTsZ;uo z-7T#ypFEkV1Bcp(J|AsyLn?NR;?!eb?mWFVHxIU0P43|}sE(0*)u*}C*MO zr-gU-KWyy*3UpblMFPLHpn?xfl<99dr2uvAW80Lkgr<+Maf7@_Pg+Je(oE9k8n)*3 zUc89Bj=SW*fJ*Q3jY8Kr!w%|=8N8I?ULHsTjb_Y7=K zt~mp~KF7>_fL?SiTji&lhI`6n77rz$Y1#&|xeDA5qI7m3j8%SLfngmw@=u#diN0)q znXdd8(;IloV1N34r@L#%wy&sV(C26&4%lE9Y{lGaShbK%n89wFDO)osB=BWSH--XP z?HtZshA+wwPfpgq3;u1aX2RcBGczH-S%1&E7X7%b z$%pTSe|u%)$YgN)3)S3mcS+Bu)WcSK-AB7)V)3g9EiK>mYAtF*um1T6?B&3sRF*MD zf*(qKe%@Gq?>iO|R z*jc=`%h^cLO5(BNngpU@`#_RZO|8CjE^nlaYyT`%ybrIql0&V~@cT$$wF z=LZ=6>~k$RHIhy*VD^S})FeY%N;1KM|6yO!VcPm^v~%$G0GE1cUh&wt8G!EWsp^1T z$BQZ33@xa6fu7G$Mk|G?M*NwrW9{0X*|)WTzb5x!&ie8rc`Y*nBqIp}SD)#$C|OW$ z&VnvB_KD>e%ATU>R=o5xrgf+;FO&}~wRwL_4@*ExpYoJ!&C+9g@|K|huX=xRJMuyH zHgLet`PVE%T{=F#YW+w)8HzGLA)F|GNfjARYX#*Ekg!Rdni>K7gh46w(*Pie7aXe+ z((LP~4fBFV^}%*+1n1urH&*Saon0jwcRMAPzl1((<2}l*=Ka0&nEYX8HH|fef}!mU zM3^)If6Mml*CL_yFx|6}GGJ3Brfp`&n_H&=qF({_I%|WM`)IP)wG24AyEatxkM9}4 zkB@Zmi{`4j&n?3+dFMUmLsx=*?ThK&{fd6)DR}tdf`NQV$-%}&B#zqG{y2Oot2}!) zPsoUZK9aDy%?~7dn+=Ez+BAC84Se7wIYNC1ycq;VQUUQrpneY!5`(_O3M)sN8w655 zllr$AZ8_*TWN3^%LTKoyNwpym6$=I+l(*Mcf}{l8C|a4exC=jQXY9a+Z4M4!wh%JI z9xpg@3cW=s6%zM;wDMx8IF{Sz;>utt{lV;?VZqlji+RzLE12wF*_+^^SFv}4b}P>u z4;fd0ef1F{j<=xof9br08_pi>$5_{GkMI4w`pC-^o3-S@YP5DNA@_g-4<%;vy1kZ( z(p`nL8A1$a0p!o>Shm_?$|h!kKNx;vMKu8z`dPoB5xS5(5M+$Q+qG5r{(=vOgf13= zi|Gf_KC#Z!#h&abv=X|aB6L)7tAuBP#8(bUfPNiXe{xddp`*%<=j~PUqa<7Pm~A_A z@zr^y2B1i zVL1D}A{LMboB5Q*!8S)k7sFl1J{X04`LG+%f}^2fMUt6ft~JeMhee1*M?khb=HP>`?PRDakjlmwTUaNM<@#xu<@DfR8 zxx%!$)1!`K&?|8abfhTOJuymW(Kdgym+$cn^x>1(8cG|?Vbph`qQvN->$Ixt+Md3f z)aRYv&)R(E4Zrmv8EWp>B#s6thyF|lZtdDg2kaF@dhYN#Q+@~Aym+>)ATIL5OShbL zvl>s7PA!C78VaU9Y-+ifC|6{A6HZx2%-5{Kn&SqK?$a$V_{~$W&5|M|de~+^G|zh; zHEZg!e5_qJ?eU8u|7?CMr*@zBPe_;e`nv-18WeB62eZUi?BE+LCQh^$ znV+O|I%~O{9yn;2n@hhB6MT2^kK9yf%zlNl<$@7OHno4g{==@Nu&<#t%VZW?1$W1l zc=xJNH>>fU@f^e-?&w&QBDQ;BTbbI*HHhERX`3zZBgam3gY$MlO=tYyGyfI@-_yyH z(O^5+si$c3lVLZzs{0me5S4}R-za{2pH`*m;CQU+nHL$_7^LOEqclOuQGg_WWzvpK z59@*618mj}U#j1pHOjg1b6k@k2iyo-t#2ERbCv*n-X;iC$~@v;8n1yT-$R?RD~HCC zgWU%Ure0NVpP9UpE*P&CV^-XjG$^8gh4d=up{jd;pz{-s~s=Ann6yk4_^B#9}YL4e%5fU*)SYj2PbY)oqT3#>&GsU&+j9r zkD3biC_aSvR9thWC{%SoTNoRz=%2jury|LT^n_P8>=j8lHPicx16hZATyeOS3#qwSvm^W-yg!Dg@k(V zh+`LR!>ap_8Kc96j{%O>U5PGN!RLMCDy%a`#xCA`=2;HU0UKg%m>sS}u>f(<#%@_n ze6O25$yIIFS)t$58^i70h&Ap2;|8!n+P~#aypUsRu!BD(_uQ7Fh{9}Q;5j~5`>Cu+ zSM5|ZW zaJz=I5pMm@KscmawoDq}G6(benzt>i=pW1rfdRtpyA}5Tzojkx<&Qkwu`MAm^D_rg z!T=5kfUz5-OQ*t~NtaEf0L;1g+l`qulyJb))ynT?Jq_70@jI;IT4bO}sL<@zX7TiD|WN z4E?W_zH98qwUvcO9_!!j8QZjgkWoen-LVY4O8*v890I#-_sndb7yRV*#9|Yunf)OI zK4UEPr;>@n;DbV(No=CA zlcL*pKgDnTEyVQ*M<`qbIrbZ`z5O})|1W%O##FkN#XkS+1Bykf(OYFRwcLb*D(Be6 zZ2HBju)!d@Hn_0{DEnmw7`xvnumv#sjlN2M%Fy*Q6M^7Yn7*v8PU$ahgQ0+KML~Nq zD=1(5!!}H8nRMvYc%vRl0oZ4m5qpff^+$rovWN$F7}vUrISJl&aRAj^9*9H;(CIOM zJs>PPbHx(a`*%rx*fSNuz|z5~&wsLiQT{J+BKkMcDIpYL7^mW7G`jkQaxV7X+wL7F zzCFM(9u{EKLn)e{OGdxN7oZB(sfxCGpzLb1 zLa-MLQZ-`~J&A)^a~WrcYkayJGq-40bbE$*;bHD!n%x}*`JKz1h+A(pXs_PsE7;fLi}d@@kHsYw0-D z>k__a#2780(f6MnyBFkm)Eu6e$e199w|>7@*Z5-lX(nh+tz^@P|+(HbqBb=67-1$ z!)ZVr#6iFvjjaWcy9>?$U%R0O_G1x918%%XX(5*Z;tSx`D8wZ#G~24|1azI0;JEAu z zCJgwsCb_W23dw8qUkAJmD69Sz@z=urB`3DXw~81E+kYNCi{diaVDbbkh6>SoV)fV+ zPvU6ol^MKVkd2)c!zm6BA;z!+E#}I75t|?l&*7~FDc!a=RD#X$al5jSIcoI-^q%?t`<>py=GkV>p4ZxE7q{*nWl*t_47S z`|-9~k?Ka<0YpQ_71WeT3=|Fk-Y8hriBoKE#ON-d`#DGx#vRM>RnnN5uSzIY_^|Vc zm*EWN6Yo#|k?b%Rfuw1dpEZzj24{!hQ&4%cRnmVN^X;|Dj@qo&3QjbeE!T||LnK0I z2&kgtMIK1-UHga!9XSI<*1{$(Lx7fImHnt8>%|)&J?Yu@HOAq?_*|nz+nZZ_J&$<} zB1vgJWdt=!1IE`V`pRroN6GR@tQPOd%?9V_F-yN!1>KU55D{*VAaj(GwL+CH!&hFW zLhaMMppHAvz9mhrh6D?vf|bR~zOr_Icdl>$lBrdB_`K&J$vbfx(TZ3?GOgYo<6UPaRuqSRIOb-RKr8p64V;XNx>qm%ucl4_o?IqxqxY!$L z$c@Fc7j}9kmk+?{jsZQ!$H1&5i47zaoq|=S zyJDB??R(uCC+Ie;xeV&wv=yqys$cZP(E;65Esf7Z0T-`3a=Ouq%4?R_%MkK-L`c%e&H}jNbWHYiDotP4klzxJ!I#~Antr3`n;>-u;bL@92~8*(qqRAlzMb3OeZK_ ziIn6G6yLolfs|72XC^#mn&~Ih zzXj&<1}rz4hM-yD_1s*-nvE7h!pC4fGRHa}n3cmDA3(t68I%3m0lN*`J$X$XuE{<4 zm<4g^>?_Z5R!*$^^wdGQ4p-IfD72@Ow`XaYW_uGek&@>T5|*vU$xgYN0wW<&Vg|~V zks|yW9fIQIBXD};Nud;PxzH6sgHQVw9_gRrIn2x__c;hbu&DVlyEpu6BQ$$K2-gB? z;!yv>!bERQSRFt_r_)q4qjKTbbV|zqhOAD?fet;rWV#0#lazxSeCqF=0Ie>+-k}rN ze)EOyd(XQ%^$E)3M@+G;Ti?sDmje*??rRb;5QGGoUr0XS*Y}djulS6y?8oz)&P`r_ ziAy>HM2%bJVI{$@I@^=!fsHh~x-35QH^>zm(yf? z$7YGiJERpO!c}`UBu_#*EMzF`2s1!PqSxF%Ev)WTiV0(~5k^pfU*Xdss1UySp_rf$ zXOOSzPWgBtUFT?{j_bg5t&56bM?^9h3IwXn4(pF7{JtR6NoK>o$;d zWG0Q$3(E6PNT$!K5*EW8Nl*HvdKL%1_E;tiZts9C4cQ(D#AVVgFKSF|n&xCVU_)}I z6JH(AlHsO3v!6W*kKgkl4VQYoFNnCedG8`pH%NK= zclqihLnG>}oL3q8MzzwZG`AgZ%j*grVU^1R6%6eXeOe!B8XC`54o{6Jpu zlh=`@*QT}L|4u{=XNt_tI&?fk+|s7Ize7{fkdJFF0b|+nN*s0W!e6iE(!5TkGXqTb z%@_`=pwyxoJa7$1cx%0IA*h%nN9Qu9r&a?vBT5d#yCpO5?Py(0N?(iAQioTUjs>xZ zeE6S!szmR0;!F2v?w+qH3k^$CQ$LR_vstg6Jz_vnOYRg|yv`D)Fj#lQ47hYZv1fH? z;sSGVBJqLpo*YRTu2oKs2mllD?xV6O-)&lX*BeiMtiltJU17}DajhPI3i^}uol}jE zwQ-{9zS7H*8N>5O_7HsC%5Fe0=*g2Mki5}zq4#%A2}0g{H?zwjXIi)ZbZLoiE1&sM zR_9gi-n{v(@?@Cr>hu=^ZFI@;tdx2`O(Xj3QtMeIqM)?8RZt>o^r!>5o;M|b8--Rz z&cICVj1k$(EyG1n1rDyYz$Jw1r~W+A9pcq9(+!CZJrQwFgYWGUT zb?d9wJk`=;Qi6#&RfI~#vt{M3lTWL+5hC3AAUhVg7hbkn7dL-<6erBZ{Eg#gxTGJt z>x@h9Y^!#zEo{C*T)LU@SZ;rOk%ayt&>t!->&oYLhPqdK@=A8hL$R|1u2(K@!hc{0 zLQe3&xU#+gB@{!YeC7V>9h?^x4~%3)$PFGlmo8F2dJr9xgbnN;DW81=kss8r_wKm) zYL}_n{f@J84pUQgX0_X3NwleGWKKySy&EbRCVYstNvp4QzjQsKtuF_q+_8a3z2 zS2VECIbnai-6_F6sz`5lK}fII4mo4feFxng$yr6wkSOD(txZ-PMor%b`9`mOO`VI2 z_r!@4yq^z`uZuja)3h!h{O!tuIrs#8l@ii%((_poZ!08# zUc0y_f!YJHIa6& z%1x`>qo#3jZQ8q+y--K2FTOq0`rvF#+w%`+C5I1tl;UwAUrXS%s!lE}n%!fgK{<0i!0TPj*6vt@1^emWPonM+LMH`OQ+ zL%xN`Rs2~u5;3Jpj+MVY=rJsn={b%8=;ol1D)$}xNgWl&3Rx018NyQ`FMj@^PH zo%=GTg0n0n5X^mC+}abpZMN_BJvEU$=WNWN3}R|n9o;(nXD$UvoT8`34IWb_QTzjK z{-R}qZO0#;M-C(lKWEp<1(Q;0oPoK~Cci-22-+%nBySlEruEzWQdcB_8OG^vLz&_mvvUw{OLnc){Jb{<_@x7aK9-)tpdgd}ET4ht0 zGoDPylDkxpp|KF{+Ck;E@Zc}~XiA(sbz(U>(GWP}HI`7K;U=!xHF$;Zt@%}o zGMrR)afRF?*cq;I_q=MO#gGTF#5Bl*CxaX~nYqakIL~D>SHK~ti@YPM5B@wPgm&k< zsmOXfs`z=b8!{eY*2jB^bHf}Upfd4YPm@LUv&(@F zxIPwxd3?$$s}zn-M631=F;X#s*&b^b^d2`#IvKPF%-E0N)jySArFUJk%01De zSv2A8VPPsgDX-WCv`m`AGjv%LphVDfCA$F*##3^>{iu*C0FLpy<`9elR|y~Jthy1IoWn*?kYIHYvU4brPYjy6M z?tPVp*K5AAXUK%KZKI|V&+ng8ZBKMpd+|H99XX(>@L|j#k@-7LIH*^C&^_~(A=b3onuB{k0 zPdbdbO7+-6Om|iCdFN#Q2q^Ka)B=j_FTN@9Clc@%DxQJ3SAVUpDWYPJ+()lC-5+4h z&}!)~Yhcn))M^vm$38GRafmfnHL)JrDnQ{iFo~1k<$RSiF+ zG@-=Fj+bl0N?wfe9A{9inM-v^FB$HYXog!1CvcMtickY z{a(+9$HUz|U$E;MhH^o>x}jX^+9e|+530imx`?UjDT_7#_%;n}$ZnQj@6edW*erg= z7m(poYfWnau#H^GG**tPH_50CV7#7?^7&I+C8|7Dl3WIcVuVMty@Kg4@ZrIXK5ZTM zQn%REP@dNnvz+JHYRF!%vNQ-#;E$tS1TK!_>OO*tRn8@g=tOQF^vE9>d!0Lzp-_9$ zhlb_lbw?d!HOe{jJZ^tx;AB|i?{AL=F*JJoD+zW#l5 zGd_%dcFm56H_mS9^ar72R;pgtB!c&=QwE`gdzt8WNFu536DeF7b>EZ&=|s_@Fy-pW zF>_0r!hQ0E#GKt0kJZnHsr6;kPH6P`D1UjQ{aYkIhWcwNvXJFLAbX>@tNJEOYB*ttC{aBZ==+9ht6PhTIkSp4_e<9&Wz zb!9Wm>cJOIN7qV5rU$yjd79RIs_Bc_ql1OSWaX$t-b0)~E;$H^=)~ATO}Wq}_jLe2 zhjho4Oe2cII6+uV;@n}t!;VnFRa}T%I6zA0IQH zzNhldsSZqa@3Q?B#=u2>h1OaqzXa?O$>;G78S?W1ah;G_Ga`{O-8$a%E~X}%BT|(ZYjUSCc>lVa}Jf8B{XNL+bg1U(xVWj!k@$W{Z zk$rOdjx#2!n6=DwD!rikb9c5+(!v`_RtD=2G&i_WTf3ntG|f<$QKB=Sa6U^t(h28n!H(iLM{} z5gjd{{PT4`rwqP#nj8ipAiMcvGAkaFyzm*n*z1cEP^y*S%3ro zQ0hJuU~6E4jVc}^(K57U3hs)l6=0^Dq?4z+96MPLUb#8b?5~*b(HN^~G$S6ZF7#^_ zPuA4_-_Z|zQHDyv4tSXEXmstM>?uqbeNpS8KayXl^>IMU^W-o_$@JqXN3)U&F$+eC z0%dNkrVvLWR?~5Fy&;(y7Bhk0buiYl`LkXGFWb_8OdgU!u>Nw`8+p|i6Z|La|DQhq zOlIQ=$~v+tIvla&pFaT5_r?=Ic>pBx8}_nn#=*9GNK{kgZZ_bBY)BuZ ztWBReKCKu3=opsGShC-ZPDi%$XSxYs&sT@PQG@tb1hRpP{)Z2Mu(=U3Vl897yJudG zI^u3Y)@)S--Dop}50dZ)VHq_Tv3N9s@w9YpRVsWXD@%2(x*bt6GH`Y2EBO2B@O5N| zxaxov5y$cgD`WnH^&WHc$>c9P=e82nFF6Ud7}jxnc+GZxi+^HW~Ux;GyFWW-{*^zH_7usYzqlX%+kSh{bc!JoTz@F5`8&)0|G`1 z1|8dgZeMBuZ2ikA31^7tFrkP>X2j=L7L0^d_(qmtA`L1ACG~=HyWx7Z*BfyvzFgF&9=-Ld0ala3UEo2(SIzq8>5ZW4XRs=wAZW#SS_f#ilZabr-3if8EmF zU$ZW@8zTNyzaf zE9XhM6&;(J;~UXABr3kD;2s}$wV$}2=A9!VcP1ibM&NawbE9QqnZu3}YORAu^_una zxbhquYf6Ho8w3L*>=a$^+_8gfj!GB8D3lfkKMf8 zG_*;Z!j3L7t8+>%G;h@oH6LzW`s^O~+nG6Ixe+TuMg^XTnVYK`ZRSFpDswu=(=6@b zPvBF-JiA%E{p6%h*5;%aHrQu3u@k3SkqD{i#1-`@Lsem8)yv(nr>qUlx6XeS7ek-U zHA+aAu2v{W7C+D?rvE4-AkQcnP8<(Fl9T*8iVi`Zo(HkT`);~iYQ{5o``*Ad!DQB` zcF*`}tc6Ol?>*jpawQwenF2?=Nrw$Vzj~x>(+ImE!;hQe%(|Zgq;@#D!}TRhnG`EJ zP?`a=w^8HYuF43wJf$@jd%U~Y%HVjL%j>4>9d4{gwATJL0qS}^Yeq~Btf*kgUEDgP zRHHp42fr>hWJj|FyRX}(8a1V`Lv@P^<(8PfCWoeqUtSF!qr#zq%J$tZB&IK^XwcLj z4o^G#`)i}ty$b$Lqwv-%z^LuwBw=Qms>jP$gMu>KA3l})+PmfK5I0P3At+V`LGbu9J>WKapH0`B5MLrm#MW}4 zarsSe1_WMOqYjNKHcH5?cBr7Pla4$BS>lIZETnz65TC2vvfHwaaw|G<`{O6LH7fPo zw270jf5F`c?kZ7Qu@JaF?!KSh`@MFWqsc1PV#H4a|07|ai8T% z#A0fg_sj)5@HLI+%r+<045nVv9(0^9<@ycOUsa=u%_@q`8oD!|D?;XFbk~XoWZhg# z-Z|KhHUSl{+QtV_@NB(+!5;%Vw)L=sh84R@C`<%SBtyqkqKb(#;Ab&h@sPu zQIOo}(tEC~3U2<9J4R&pG*)p3+W=vs&lSb2xro*ME#fQJV)-Djd~<8F+VYpp?7AIq zRNrjdF}=;(f)YlV8jr_mJ;CpfIc`1~@NmX`*F>Frd*9J5GOc@(ea5X+m->9tDPsCF zWI-WT4`Fq!G0m#a=*nGf86%6`2xOJ}x<|JO4i*p8f{_;8M{yQzAYD+wqB2_w%EY1Q zB{%WIprMu4<2V1FF*DI5h@p1Lt}NDq1a~76nA91A{N-yneyuv8q)H$Rh#wktT#q&D z?tVUlRh$oGZ->$0I|9p9D}buzsK*C1S6p7vTC*}}vhS_N2OW-+nXe5I0A)9hnzy`B z9kW!vL|5wLr$O6KYI3xE*t1ff2}MOsNGmoP_3h(I+^*7F!5Gz(Z-I7=cN{Zsvu65* z=dgEZ3A0w)bv#xA=2ZXb6Fd3=DR~!MV8l-M=IzKeB(}8Ioq0Uhx~y0>URbT-%B6!-8>)EobK%7CPMLxj?eWm?av)+LiW*X`5?4psJ5lW zA@zW0shV~m&N{O!y>}N|cZ$8e{4g=cM;yaQ)UqVVGkSBtl;x$INmZA5^cBo?Oj3{pR0s`Frd2cFVi%Q>0ztbeXWfWWr8FGz+pF z_(qxUBw8J#X3EFQbh>CR*HIcVJzt8w^>)G>@(c^#92`3m#Mkr(VebzZ`=R#Y`fA>1 zQsw+{%pdEuEG0w}a+&y20l98QU@X}VE`4%XhO&#B+?zV)VP8u4rl9Y(0c}Ndgg{5C zA;L1P-M5e*V!ghc6n^!nAJ`eYfpV_S=WBoHUtvj2Hz)yViRoaSF7nv5LLwHDbU+>A zd+M|xCy=IO@h(St&z?>t9*_;s%7fzWd+U(u{yuKX6TyV#Z2@#!M zDi`~+vz+CHAn>#M4%Z~PsEt#A`WfIv_KQ7aAuYJ2$DF;iMLk}syaZOnVJ21dKN5wJLirNy!?v>zbS8M2) z`Cq#J_2J|Taxdtdt=3i~1zc5WILoZGcI771U*%@~uXAf#2A{+Ek~p5mbQl+l#@p5y_C`_;fDRai` zXggYJ5V=#%sGYnGvFXP%3G3vZ8?1bpp{LV9)vb`V5Hc92Q?3U?5KG9}7?@4K!kRaa zsiXAlP3zg~4TnR*jE(L!h_@*a1E18G(P52kFB+U<-Lh{2xkTdsoz2hvr- zXKZy=swKl&J~`{@(7l@za@LkC*DP6cw&A0~Mu~$O&B8SBZl! zG??I9wzE{wJkQahZz6jC?O0fayu*a>y!Ak-X_htg1}S)zL~DFlrZ9R7ZCL597!l41 z3%HIKHLbK5G9;eNh!Yt0bQO3CaZ{30Y41xn!e+GlIK)9vsaGfPj2hL$T`wAnyBw_< z)8TBtsr3hE2Out8`GELz3UQny3`5jUol-Ta3KC47mJl>|YtS-onTd42*j}k|3RH!! zI=AmWve@5eGv*U3*>|{g|9}$R{ajaAV0O=^&Xsho2O5bP?jzM!&I!{M`Ofy?H2Ia0 zpq5ZlEQGS{aFp+B?qre_EWAFmgXKEcR9PB2FXFEfOk#O>B#7(IIWr~#V0Vf~+zIKY z3m~#&NZnT9+%eiMjq87#rK2r!{ar&OTD9^#cSdx4#v7S=Mi$Ys=3(@m@S+MAj)9AA zlC452L&7u-gm0V15MSK3y;sWOl+bNcQ!I)jb(K?%kh9G7$vDFu)wTb{(ItKFaWzN#7iQ z!GqoerMP~$Gb_{BhDqjyKN}Q1h8aZj=Z%GV+3E8&S;W?w;+6L$GPb@I*u+uJL zLhqq{s5Bt+lLk$(?&aHihnTfziC}}u(jkwc6N8*^SB8y24sfa9ysNe`QA@LtTKDln zD-K^x3%Mde0{CD-T=lHRlZ5RL8H|Z@kE7$Kd&f_!p6IEzSB-}cn)Ob(fvaz><1FJ#h zrMNjR&^bBCJyczW*0r%QvTXk@-Lq~2anQXs%3~|!vrSi4+`D^zg);3>8UW}V9eNIx8w7xXTJ9HDBE%_>_i8w=3}n_(cVnTGBXJ9hGo!f5!0C?vD0Rt98i|NJ6_J)rj*znTfj^XZxf zbd!VSE0S4mN6+)vb>UjW#;UJ%C0cbgpds)etn`6}DHR6u3uK?Ix@Nu9tPNe}}w!Ommv>}*tTl!w0@M4K8$_C)sAj}QD9;n=2h z1|%cIrwc(JMOxRiRd3V7Ua78(d~VPhol~X!5}oKFZ%4csJ=btJUL~k_#mSzU&kR|R zrN9oZ;))Nl-3QmBeIT`Uaq;Gvz^KsB*ATdj8RX^oY2X*Tlc5r+@aGs$!wk9mj)UIz zrkdqbNBIx>*wK;?OT3uQ(Cj_-cK4~CFv|5F@5|?Tg0Ei3!(@z6u^E6T!=2HP1ORUu zvrBhwtR+ zgz~2Tw6zK9UQvge$LBjZz$lqgMI|;frXB=VQrz7zE|pikGVBPj%)HFhoG>5d=Cw`5 z%FvKn=X7Bce(L|>`P)-2+a%SeLgQ36l3 zha>^CAm%D5pPSXr7rX3tDphZ6&7bs{dhvB@t|hoU&_V|@c^=V;8XOt4)}T|LPDLkr zJx@myvA(ahR7%U}t!I1?pH!L6l|hMBR|&>mz341BJ(CYY*LiMx)7U~140})9CM5Bq_*I&uySp#>orv3rH)wf=DTIgwGIu8pMPYk zpU-4j&>-J6tBafE`Xee-8H_Cpd!{2;ab9Y#ja;`hw?=kaj1<6PoF9#=GAfZo3a0b#^ti;v}{m$70KDv*AKpcd7@?K19iM zmq8PLe?fe@fva=|V5c2AXt1eR&WK*<9D3zPjrq8Pjfa(`J2iCg?mAM^GaZws+@ z22}|uir?Wx58eqYwM|7EKV9`-ewu)I!1`isvf5$W*ANm`a z4TP_khmeaF6k^*ocJU+D;Qc|z(??G(q;KMA*XH1u6%blirh-}$Xx%1p9OMNM37;J@ z+XtE6S@Kw7ADh7gKK2kVvMvpJ<33laa^Zzg8|MU~Vmg=tG9t_`T zSE=hd-D4}2O6?q!`vYzZhxFR9k5Ua_PMB+tinP#kY0hoJxwgLprC7PkDYKZ8Q*s>n z%k)k$mnG3nFCL9aK5G08sQusEH10#xrTYEs?9|gGVo`|@k&+~%E2dv=`_?&W_I?5+ z@?gjepfGa&sG74M<&4U#!AmgrFO+`KPL218fQ604vhF%NPQ`<1n4Z8 z`zT|Y$Uqvhjp22p z!}%EUIB?n=by#&Em@od4-TdaH9*b4xN_gT5!St1StX&p@^Oah z+QRF>P`}>s%H5RZBc41luKgF5V95*b?QJScbJ}~3U=Bnl`8C#ArI?h;%B2Spf)!f5 zAAR*d9Fd;>|LTA8oiE@W??K?OknU?`=sh+t9~1svFsEW-&1Hg_O4O9oV09)DM|(lk z0pNFrtZ7F(!Pb)`##S)JLXG^q1vsg%d^GMu<|pvaLvX(tG*$PXQ1SF73duLGK~bq0 zPl6wf-ojp-3oL51P!IRc9yaA#u-YC+R`J5IpBgm_}9ud*c>`d>u5 ztrRteft#C4Q`&@7D9IT^eyCUtPaZFgc};KUzBbTfCCQR|$h`=M2D^e||%N~Cg>8nKbo3B^V)fPJ$@H3HjlOuUbs#lF7m z6-{@||E>`a*{@JRnS!#PQ4Ha8VQJW(t2?YsnEs=Q7u(lhXW==iC^GXt1di5?FB#g8 zH7tmCK(As%@Y8d~@#lIOE91 zbIuC<5YAGoC3S)Z7XtxFZRLP!K^*L!k%cHZ(a8XL3-vyJg&hjnn#GQBpoH>;a>}_) zi)>m%56g>SnGjZzs4o*jg1w>>;k0)bRlU_-H|4Cq_8+W?mp62Hg#WY4uQ7~`NZ8uW z@llFxTN|9<#K>Q=X?fku29}|}+xFlx(5Bfsk*sJ<#`NE1Y*ng|V6x*m2YboD39vJ| z4R*z*->#LUSUrWn+jq}pyzC}guYavDjfIb3)phCE{&Zbem6&9)C^19it-`_ox&<)V zQnbF5v^FjiHskmu?vp$J>ra>gI#RkpxosZ)&)+MC*jQr2X?*p~A9n8O*e0^D4Sa{yRIyqw;zG_`s>bAC~zP=X3X`@BClF6>MfQ*5`zH@?uGkAj)#8v@te&1BU49~+D z7!aI$cjC81$M%g1G}|}UH1i2rH%YCH`13sfXje7#UIri{wo_q0iF+6{twr@eIqu5` zojxZE>72)?#;woYV^YSOJ|!}frymj8nOHo1Fn=m{+AdtS;qHY}Qbmb}hDK#6v;1nw zq4{J4-a=xaqIe4N~FzuTrIL%fzvHuBUiVT+a50C2Ij=?p-!=p?R)V6eUL2j6uKNgHbHO9wB9Zn|RNuR%308k8 z3F9r8(Wl1dapUTs)881wc>$bL%JW)hL}A%PLFTaNZNG)1|@DdC>o$w-lN zw>GfthP8p0+b(^A95LVE(56_8(MHNP`9c)|f&CUvyHBGsNdqpfk9pQe;uC?nU#5V8 zI}f=9)8AhfIgxd+b#EnUv_M!FburW#H{|0Od5TWL@; z_Z1P>4`eyDc{pwL)VAf5py4oK6g$jl(<#K0VsQrsps(%VwD|(JzbfIS`vOuoUT=`C zl;wkJoh4P9dPmN?*1tpIK@jeEWM}w9u{01+)Y@#hjC*8%2Xw<2u{@)D9&_oFb83Zc zJ%^;m(h#`3;u);14ehexJ(;?#-=iql2uI22Uj~=bH%vt?zl}_E+uVoVk!O}yEM~yq zgUr7ZRr21RVOH)dA_}fN@UxRF$bf-&ctywKR*BVGZOh^O(mv6N!P)|+!5B}uE%})! zo)PQ$&07G~6q)}Q!EpJ(tadA)FrXFP^x-XOPwO(2S=5nBLDs^1A1A?`+nx9zPsQAR zA1s~Cf0E%YiSltFHdlBKD*7N$m|oWCV*AQ(K=_l@d|QpD`M<&^woHy|{iuFZdJb{o zPIGWpFPN~pY?=Y1y+PaM6~v1hy5^1RDEN51H^D)ThaH#*-VA5rl|KbLX$i9*o6hYJ zCgzW!GmJ2Uj(6qAp(u>EQ1eQkQX^f=nfvS?c;FQNb~BDWuWx@)-Y`D%6IrFGLP6iB z84%qBOhj>pawR45LPC~YlE?amjMF3t%%<4=Dq*_U(5lN&jb9W@8rU^tyEZh1!)nam zyeJd@J+SuT1G$9Y2viy@P8nP5OCgr;0pjX;X&Y;g$S6h-eYNu-!^r>?E!Gk-e|xG= ze@gzSjknG@V0`P3lzD9NvPncA5EpD+Ykrs%c_Dms(fJ{?@r_A`^rXE_)f;$npzFi* zMx%MAq^e65Nd{QELHqq_7<_olevNb>ndZ{E%iqeTrCD3&{)eaSvLzLGbk6JP!!JAf zv)lVBQ<@)pREBSU`Rf_F0-@Be?~2drozs4b`zoeI^*yfqp>1$B+@#I-d>ZLD_JWt&ENfKj_MvKn_c5r@IA2>e(Bd@1GWu zoqH}p`2|`H%f*koCXZKU+}clQch4z+x`v*-7(b1qfO(tXt|%4aJ&imgP;@&)uuJuR zP78qgnhQI&+X*`|AIM=9Y4@2Pb~wfi88hb`7l~w+e2r}2{h6Vk1$Tpd98^6`i$K*g zboMeT=>i8+J_6w!xl;*;zkB{5xrZd#DsGi)Vj<|%a3E)52CLw4_o55`E=tpKnFk|% z^F6N*<4e7V_Ov#1&tG(FF6v|W-upw=y|6eKk7xGO2_cO6Rr&~7me#x+`=lRmOjz5E9*^Al{^>g6s#l@-VKgK+I#yi4 z`~4kAF5P0N@pMBHm09iEeHar|UL0hXRxU+(g|ViMT?&g(nr~K%9hUUwDVoR&Dno-v z(ATUZ@Ajl2Dc7gx?>MEy_~HC-Eq(MlaIAydG{^PQ8TN}4b}4z!+*x{Lr-;Fuz34s% zMcqUbUlyAYhOxT3`;a*~idc-qA%*+%q_FB(qFTDd{hV0odCfWO=9J%Ik=>br2Xap{ zHS)8qzoH~?MAHi`w+Rrt$R zKB3&kYHp%8pI5^l)8|alx#qICcwD`ODzg5J6*yu@VXWf3eI=-F^*GxI{cW%xgnx9X zpzO%rnYwM<4pyJ91dX96CH$uDG`8ss$^us}Xyw*e5t#t-SbutT*WG>hf> z`LP&Wl05I}(56jyLu-yME;1EkROv{nZ3~)wX@*)Q!37ff4QLFucIoqv8lrK1LtHEF zZr(NN;7eilP+rE8r~%NE@EmPb!^%>#=a*MPO5hVs4ES)mSp^_Rl(=~u=t~3TMo*G2 z1Uu!MkR$`X%Qks2*CS=I*2;$Q?AXZTHjE2Nwu>kCAC?*Rqh)65u_H3Rzx}W5zoPy& zo=?L+#$_G`D4Uhr@`=-zw!e_(Kd3d8GsDc|U@a#UD=X`11tzsj+KjUlC&6@7h1at{qb{{|W?t|52y7 zSJ-;HQM+Sj4nQ>j5{mwhUq-@?Sk`xnL)C$y72VYjJ-Ta+gIg~S-khsy8jNCN?uBiW zBQKnPdRmQc+-?yQWS#Yfv6-@tLDqcLFI9B;BiSMD>|p@bjS!8JpC96^=&N^x^(sQR zjH)r_hHusAEqHk%1ibDJq&~0v<&&TPRDi{?e)`%6)&rk#%7_ObrLVR>2!+ZAkwbni zZRD#?IQ7oN9=Yq|Y})fh=Y815Y8RED)hVUWnTnzhV6-{lu;_!i0?zZbCr1<6Eq?!0 z4EmS7D{zOll$<^SIfVr2t7;sv$NJGT<#lxc(SbsJj+Ar_+Y-O>guQVv|vL9-uDz5n8vzv~u?Qi3=XVmF3 zE2El-`0nF0H2ajzPiUk5^ESAbs~l4FZmfo7or`GY=4I^rL>NGCas73iwiIu zvgVknn7qG&5A0y~LPs$KPj&WDYn9i&f-$Phv;$ZkU-`iV@bZTlqQa*+(G*lLRw6it$G0Ybf=+ z%=L8{H)E8vU6uOL`l1niHfi>73|brf4T~!ty>I7HN+>P7?!Gy?Fyjv~B^x&*{@)5W zwHC(yS~v-Z1?*P9BU&ybxjBfSX0D#u0t+Fn3{XlVZsLpg(FPN~NY4oYvf2$-Y4~?k zdGX|Sf_1^o0}YX)zxGZ^6kdj3I0CMtuNw5`(QpsBkd}Zpk8dlp6;QaUh{D(!O+pHi zaK91{r(FmZo`3B)>i!bYEQQTl4fOy%A5 ztp$C5h#m7zZ=#8cG8W_jF%k4Kg$@>itruuTLGt>vtOPVH<7u$kB1oQ5N-CL8g3@GT z#nm96Yg{ta$%;?osqPk3p))Y6xT zD~$Q5I0govtYi$$g%R>u=>1l@1GP>U!Y>%5c`3(O70yc{;`;}ShZKiYRl^*sT}u7e z6fCE!JXHIx@W~eCc<_Hc>GnVVgClf4oQM{8t-NCjImQy@dW7EXedpaptU-5r#}M^* zkljgW&}-sxjxAxk^hcASMWxy51oEMjLAq=B<0RRZiozMDN_@FwNINXn_w{oGidE?_ zWZd3`D2X8EuAF7&5c!@pJSueMiJ9<#QQyAqS(Lx|&Om}F=xQ}tOZ#~+-2KaWH_6AQ z(k--fB^xdS^DQvFVNX6zSX|9E1Cvsjf+)0nAivM^bucVblz{dOq4p!OZr$}|1Fu%! z@+oM`jTFp~SDLlQLc8=ZB1Z^tkaqD1rI@Uz1>@K7?X30%E$y}aN1Lpbqeuh+?czDz zfXRrz{)l&HyNJR6AT7mad&pzfk&w0^0{#|c?`R65AQutzLcz|KQGPYPC-yRVD8XD0 zl~!dtYOt2mFMF?rTH9ASo&I!GTVh}x=SB|+lvIYk~-J@woFA}@reNiK5 zN|xNn76oMT=b}*F_ZB9qFTR?E>kOoY1^VIFGu`lmsbrrAX2KQ4y+Kqn33Z|=lenT`Oh3pBoP1|in!paS z2Hjsl*|%;6aO05x*_}K2u<#$pKj6%Wayal4_B za6(XDGFs4Pz9D2a+iJ8@^hEC=nWq!3Xw8u9jK19HWNSP9-nm?~BA`5L&{$WHs_Es$ z-LLyg8{$3Y)9oC3HM7Va>oH!F?~XD$X>LGk76t!<)}#xM7wyvbwr^@zJT272PAIZF z+{Befg_J4RLET)5QLdO|HDtgq^wi7U685;5*|jniR6!DV?yaUc{OJxmVGfgND$4h@ zF=+IzwQD_KT#!~#V(COaoi!tYoLGcLrKg76`429Q#JkYV4}o#+E|QCvnSWKyN`=6+ z4;S3SX|+D-5e#hk{MW# z|D9dhgRo>2S9Dt?O3kNHE27dRx%H!o_F+2(#*DjDn!5smV)q1GR$$V8RP~-?bvv7_ z)ExjW^bq%T-R~s^Ysq}l^;NiBJ=;3mw!V0QcRdOE+T-fYpML=zG9Y0NKFDQoZPFn# zjMKSF;37?0_xnWOo4(=u+ZZ31a>#s72OE6$(u@ycEEg~86yn$_j$H9|i%HL&k_Ef* zUQK7Nv!uc=oz8OpVvCZGo$qj;xwLb7Ct?$zrxQ#Yl-SjK@ar58&RF(Emfku}S{Iai zt=#ltxuM&;^%C8UG0zI3KzY2mu1vx1aD4o9ryNYY*sSUD3MgDHN%dXim!A)x(AwyP876k6W8{^vfnLXiFQIy*d3D%-&W=!H`9i5igg)nM= z5Qd;$P?Qx`eGL{gze;(AJD;7)L;yZdFtz`+7JXGWYo%Kdt049}7-t!F2Iao!BD<@w z+ry4szk<2bc>Kb$`6+a2r%Q6=R_8$En@l}Eh;le&NaSmkCS5w@ob0)My-dE9tKsm>#f&B(fWMJFd0qX6C+C#Dw~qfHpl96!~3)&`u-~xxg$^ zY);BSETv3enYf1_gG=M!H#+lD*OJR^>tuiOtViHkljGV~`w|YpI{XSTx8Poh!H%e> zO_ix-;`&y-S_O7`kFu5Mp2%=8K{q4*eR0W*tuOL^vcYcd)2&ia->D33OenFVto1;0 z7&%tPN#8?Uf0-Imt0;p_>+*Sw18GPTcfP4SxGsdT)&IRSbRXy`#kw?ow;?6(8;{Lu zL16`WuZya|nfBfVi#xj-sB?fDD^A%>g5S{xa4)7IGC9RZLEtW2&H( z!FcdPHTitP-s$`8vNJzFnEk+;T=7cx;B9WtVclvU@J)aYkH6b3Jk&^@as zp@ZRy$GhAD+W?!Jl7Hc(6Bl~|`b3HqxG~C^6IK3`T|SUTo8h~olE|>2OnbS4#rs3v zdt%TUa99*6R+@q-H!uA|x9a>M0Mq_)Tt9nImh=g^c=o94)s`mEhfND<4mcg2Lu}eR zWDKg3s~v6_zX6?62tACsh3=pd{kI}8JO5n~}C zw%s9bdG`K)^N{fABf$T&rTj?yYllnun4#RARWOsban69TB>9ldf4V-qO^ooh{&DRy z_z<|4;f4@fX}@xx^0lQqCBJ?kePhi3yH0QPS>AK)ndbcBWg~gnmu`I*0H96*euiWs zgCnwibCJpTUR1pyP}w{GXg_T=`TXIm@yA2#RfzaEYW z^1q%fXoOF5dVy(=jb7@8BK_@r0p((PXZe=AZ_))v|F^3EMB|^XZEfAWHk}=nzBKvo zGa|n)9z1?SmDxcH`1TD(hnK*e?`CBo*pAB$!7fQb^{!3W+=iqHC%|Fe2b&Ty7zF-q2asr;2tFF0IFIePNj9Kmd5UJ?`5YMB&lRM?9yFh%41h1fWDAGo6Kl?uQT)pW&sX{`RnVODifa&{D3-#ULsm5^C!6mGAx?$@<74@)!JV`apEOKU<_|PbSro0WSlCa z;RMXwQN`v2A&03Vd0{;Q{$l*yp2kdZsa9663f6^6FI&o3c@Uk12N#SDX{JpCkImii z(T3;ryEN({$GX2cYpwa)0x95PJ-}5}mS0?0CVDi!ySR@vkGv_$&$E{!G135aKSS;w zxAWZEnVXzXcepBj=pgM4*5*2(nh0))S>qP5x|raXxQE<P z=lHupGtG@TI4)l>H9U5PX989}70h2m=w;fM4tT;!zp}2h1qUfQlJ6pRA)0OiX^nXf zIt>{$ZpM~gT(6E7c@!@K`s2opL7Asto2*RYx?4KV%In$+pD5K}2BaPz>YVNHZfnxP z^;Xo5o`tVv*%+A26s`=fAsp%XV#DCx-QL$7gBK*9esh7>I0dBS*moOn9|DYm2?mOi z+>vW^tLN7a-nCr+(8K2f4;*b0|CH{&__?RP3@vM;YIO5Am@_F>g4f&pbwh|H%VQc0 z@k5ZKiS1gq80W;HCl?1`vgR@kq_&PBKpbck zqXlUDw?0D}C3|eq5VtLsOT|t1f8Iaz3?C~gA!PyP`AehYdQu%y%=o3BLh{`5(g>2| z01{QSXv~rJ2SqB7SM0lG35R@E4hF-Md(UyESXovP`rFcv2r1t=nXl}vx%FD{ zn6DT=iNs-23PJZB@u=HZaCf!#>KaTm@Ykkm6h25{$zK+ezki|nO=fGj^JFaNNKGW@ zbdG_udZ!xNrh^Z=#J@XOyK$R@6lvrWV|flS6bo+td?G5vSYD7swjDw?e}3GMHd7^T z=Y7(tQTW;I#oCuHjrvYc3MRv0=~Hn@@om9ym5%S4*7Z+KySzg5xNzB0l1o??v~O$Y z1Qol$7>(g$l$!{Tn=tElNtz{R<0Me=sz7g0j0se_siFk9!ZMyg!>B!#ugEynJA{nW zo^jNk$^zBObFiiFhM&FuddrRuvz_KAoE()b0U^&iiRpk<ZrvM)yM0h2W#|V1==z&l#zA)4{;Uy-VoBV!cY}Rk6%WM9cihwUOd1qGk0JwmX-wj z9&Lrt_Zl?4*OgSDTv2mAqT=bO+4c@xhIg=8)5hN7kUzLET3ts|{AMw+r+73L0p@#WPW)E06?G`v14%#u#osYiU*?xuBh0lokf^F9O_IR=Lc6+ ztDCA*{RNl~uY5iYjXNjYI7ADRoL28ScbbkpmJ+PXKj#aErJccZl%u8?xvG?2=bRe! zQA>rM!%PQM+{=X*8sTSKcTu##z>~O?lee=IfAzD*Ak*5O`oOk#FRxN_WtZYf3g5gl z$mz1xGg_8c@y?QLhpB)NQitoJ!V?L%1{hkjc}ojI2-EU4b3!~f-|X9>(GVFtyV z4+%dS#5(MEV|Um9D=?I>lk&l+>q#LACWw{bc0OyA%B;<~?)zS(vbR5fvR%T^E^A@8a~)j>Q)mZQ}t5-?vaAPKO0S=DPrAAkYF%< zh2B?5`Czjg1j+D!rEhwK?{rVO~S z_We!90CZnByo~Y6XC1HZe0F8dEa7QJ-p~V$-^S0%hpmzFlBTD}m~BgkhjQ`IrVw3; zguK{A=lHY^Vz{K@w)sXvJJo7_06MzN`eYtYdvyyKA}hNR(EijtKea4 z3K)UkewH8Pq}MM9l!TfG!g;)-@ADiv)L%=%+dz(BvYHZHB!7N3mnhVm zO^dK%>e8bjk_F9q?xkh5sF-6Dvo60}9Gfm>b;@kbr~j2~CmGho3KI8XLV6uv7VY z*van8zBuUBSm(>h=+9S#wc7rKQ_kCE+yuV8zF9g~po8sX3DGbe z_ai$54v+sNeUn-27}azZuYEbk2j>#fXjC|(b_wev2LtdUNnIbgKIVqv-P1SUBmanP z_!GQsXq$_5K#dl=&?-G}PFc~Hl?Eno!?iGAzvl~&3Hr^J& ze<`fg+Oro4TeqVgt=bo)ey|DN2TETOlx*T?dF1NtI zqdmvs+S!-4!XT&IuDH6>(Um;WI&|HM1X{$AKu}Fp*Zb^{kNgi|@ZPvTk9C?~^HR=m;~T&2*3VNU1GIK6Mug%FI$x^DxLF)Nbx751?3|X@ zoiwpOh%(4=`7FfmM{$Nr+)oRT+-5~D>eHzktl9vr5vKOxcNgVC$Bt1-FuGohU6&$> zJ|j$p2}ye5C#>YVoD=KZR>GLPy_+ab&jdog{n0!7+Koh@hC?xu?7{ObgPj|S`2-p( zfySaZRXTN8D)1_`sh%pXa`!N8^xvsIX1+l!*OkvSj@Ve-yS&m@8de}iVMQN!t| zLYWmW({{LZZ|RE65g8=$&9ky?@H8KdXWE@UqKIH_dcDAbTgO7{eqlL~i@^oFudWsS zl}hUL-FGw(PyCDF8fSmn=6O0Z(r-MMx8slfHIZ!v9j0Qah^QmYY}s!FVV8`a-}?LO zsV;#|)wcsPd6so@%ey;pp6lXcb;54n#m$yhJT{M6ERlBX_C1_N8H-PqT+Iq9-jUW- zB(v2Q-2UpnKQgm8YBvilv7_jNf_wEv6N@p17TlUqch2iZ#h90hW_v0Hti+kG+u7qv|U~{o!cy)Q9f!%Uk&bS65VHVm$Ji+ z?1QfK{o7h||FNxieE)vOE{stL^<9A|`am2yrkYQ%jUsDDX!F^z+L^mxv5EM$0_OF) z(^Ib9F#Gn2@F(|j@vB|B*Vq?Oo{t)O;JXVbBLz07)zNT@)3tG)_^h9>%+Z#)pu@v< zkftoA=$u|Ar=?kFKDm|H`FOQV(IXR!IIHnlN#k}`)(JyZ>)s@MZ~@69Iz>_=@p)Rn z_H=hx-Xxoj-GYb34o=vGP}dhGx6>X6&MByM) z*+qW09*aB5?YM~4g@ISZ)Dt#RoPKD?HrE9W;dB_zd>9Wzq&Tn(m0!qhnZlk*E&d6s5tG>LeRnO+kTHIT9>1FLqgman*spnkFlN0;m_KZ_B8_Fwu z_@j%``8gP`+sb+iCRQ!Hk+S6%A-y-cy*^iWlX`FgHpvwx78E4a=~E7~b9?APbU8{%H7GpS2&Er zVyF3cuQ9fKtkHr(Qr64-b~i_>ubQ&QCJV#IYlenUs+^9svo(Edl&gK#U0}k&Y?Yo8 zhL>6zi_nBoECl-xooI`4QekfoD=KeL4c+71` zrjF2Qi)ZU}zMHHJ&yrvbu5C3w42;^p1j0QZr59W;w8~B3VpNrlLtLpjS@~bFvOGY1 zHCP{Lx|i?{2W}V!0s`ikBSchHd;VSSzq|YYDP?|5+FL-f z$o^b^|1i09`xBq{Kf_j*i#Q#N zhsfC>kF9R3jr$+9tH+3B{Jt3-eg}B zoh)4ZeN2%#)4b4@|NCtxC1=OjRluV8qVui*%u<9$7on=<+y)**R%Ff=Z*SWqK5VyC zPWF=+FkynoobCVrW6H_eNK3=hU$)FI`dPL%Bny(9EaYOp3+SUEBsWQtO!8vVqItP1 zCPF*r01{r{ejP~gZb zFHg7YtEOmi7au2RJ8}{jTYlbpWcEC81p!Uw^7t$FDAmD)OZ%fBh93=I2OpS6q1)zA zsBrAV=;KpMQRx5aVdb<+ZC}}Y1c)O1q*DX)kQD?vrvSZh-!;^8+W#@D+@ELlf9F9K z<-NqJlZpHRZUO+`mZ}YN*n0Oo;T6t1b^Keh4;>1wMC-s6t-8|mk1U_}|QdF6Mx6Q31eLqhLDb$oX_ScpSt+49- zLr3S1@BA*%@C_WZf_l*Ota1+-Te$9pq)q;ZO1G3u8CatGg?0C*>&jjC0AyKV4&P zo1}Eh)^nTK{BS-Rkrqa-;EkDP&Cbe3@$RsVf>gqPavgsQ=`C_yn+A@hoGOy^o^&8+ZFbZyp_h(ANiFLFf8yx2_7OH%b8Tl&P*|heNCmwdskV*Tbt)oEZzTz#&YPYwNlNW(q%Ys9b7dROsVz1 zd}<w_7*r+kFqgIK$YUH*+R~oI%baZ~7j({ua9TU-b_ygK5kw z5L>IE_jb1$FH<|zAZq>l!=Elmo}K)-CpT+M`kI`bNc92Le1+@)b(G&2SnhOuQ8go+!@YVg3q3wx&Y5mzemjv5Qb4lQ4GyOGvR zt2yv-sE>4NB{+Hx64EyRDWuOGi)AIW)t{w2c@ZL&ePW+$^>VX8#_!ARfiJI5!?{ROz9IHJT1~`fVZVO0cB1={Jf>rZ<6RbaB zV@>I$-OWn$vrACY@lf6_Y>I}%4A(_AQb-p-myTZqcsDYS?Y*tW-HKOdC%%51M&a|K zKCB)-gr1>#*{id!iQhsp0&Wk;eXPOgSj zZK=oi#NvFcYBXH47~%CP#dsGD^Nr)6Qj_(BXcskE;Ndj=bp!4N!mn=9sIz*AEO{~tf+W6 zm8Y^8!3&jnZ}I3J!8guTLqrE`pU4-Sv2T{3A3u^;-5;cQIc)ZhA0p_Yk&#q147`I3 zobuNDPe~qE&F@ml?b6Nd^5R5G!)Bbeh#AsXZ?Tbq?ekwtF0|P)K7Hcbr}ZE@OGh+L z5FM)8D=Suniz28&Bil6^t;uT^Z>>;h4B3A};c|~a8*cq6>8Z^D8$+Dvdavk|Vs@Wo zwn3n(n;&eCDk!aqTzDxA>QGr^K3)#yj{O!pDsg#uDSe+fN@KXx=6HbXp%7@qqSm*5 z`?qz3*FHy6ab80F(hcX9r23zwRMXUqNheif|1W*>)J5_ixS z?1|ruHNHV#2Kmu}ny|Rm7WtA@)4~QaUX60V4X;CVImj$<6XJ&4pdUaB%n*-=U zU7kv=s`*AORUdL?tUO2Ys2$X4183F2ijmyu%X@{=6S1;!Bsdk@2mk5IPd5yeF_h3NsW0DCT^?*F*Tx%COtG%t9SD*QN;1z?)6V3;UYiN zOJ}RkNo9x4?ubqq4HDNHf67A3yninlJL&LpGT_lObMaF|6ANAr^KpjJ_=#?$>rrx{ z^S;Q+04uNF3U)+;15vA`C2lvg_>XX_kh$#l!o<4!O&V=sdyK2@aUi_89Wyhr<5XG{ zrz@&`g|Z&}-C~87;3s!{FQyrD$E-AFQ%NJ|Hn3>9GN$7msO1tK?cAZ#T6EkYI#X;9 z*p4RtU!gPpgQU+rdSfPT$f3(c1}B6ai~^BMB(=*@^*$~?l{C@gg{z=tYJp-XI=pX( zF&VurPoJCaGoe70ON>sbKM6uCVd|do2Gi>bmnCD{uXlv|CULXHP)`j%!6BjPiLFAX z2Lwd#o_A|s?AYGRV*CCO3!KJ$qdG;+c(Rgav-1Qq#`Tq94q435neg0QMjLuNgHHG2 ze01wi8|DTGF6$caj3hGyqTv;*VWIJC`NatHM_GC*KEG_t0(QFZ^r2h~(poUa8^576 z&OKw^vktP#>^IFVV!Ne@_9YkPx33OflQlbY9w!)*(i9n_F>1NhUpBjTR<>}v&Q%=1 zg^@>lt9;_j#8B#ILzRfp z+WhS8ZaG8$Bc_S<dQ(h$u6SBl#%k;fOahc(k-v5&-dNH3|z zIaJS%vyE%Q#@6<0n9W(;XR92b-Tz$9OydT0OWKz}W(jB_@7A;$}yVeGHTXzAH} zx)T_>dQ)C-&5dJ*M)iIxVspaCMQ8v$zq2jfRUTmTOAjqT3VlQBn(DFYTW$WEcY^gq2 za$J(k5|)WKU62(7!=#G=m3C(GG_-%74{+6dye zSB<-S9F}9$jwgF{Rr1LDYdUflX44X!f2I69paQvFvUy#a=e3a!*Vm5-hO(tr2Cuxc zo&FGL#Xb`| zd4gNJFqb3`A1jXREM~TPD$Xq93C`BAw{wj;8K?S4Om;7+wqMuAwTlvTRS(_92wR}&AVP?W234>#2 z?l}D+w|iZ6ND86L%U2x5!3;7&dCD4JWVdCGz<2X=+2AyG1>G!8yKGA+j~_(I3d3@i z8Qu=i`2F2(dBd$o@m|NnUt`{`r*`CJbrn)x>qx}5dpt_e3#IUPjkYw?7=|0IaUU*j zkMth5Ur{z#UIlRD2hv>aRAW{|24gwi)mx9bR#Zwi zmz7!X%OyBoHsxU2sq!iyRuyvPWM$W8n7|TD8jA}rA7Fs%w|+?3HB=)y1sxTFri7qz zw-mDKqEiCSj!W(>+I&1W#YCA=7(97pQu*VsX^gwMs+;4GtE0TDkFkco&Js1E!?Pkl zB`BFA9Xe$PaK)uNx97^30fE+gnvbsBf;%_fT(9i|0{n*E}DLbe)K$w$*hJWTB z7%;P36d|z)(34=&~7~)Y65LeterrW1u@0nnhVyw%R;D@R{DA2 zQ>ym?F!Ip1!umOu83aLmU=3b28}nbR315tG;J(c>mU;z?g~j$&VvUCqQXCAw^=-K! z8GBm=WZ@PMiBMY3V_RKmn5i+S)dG4Z6iH;* zRe=BDja=dofby}c!c6WiRSVmQDD>9q(zKX;c~d2l!E}66^symONwJYKv9scJOTW$o z^uw6nXTqoM@Qq58-xbEJV#$y99gEw_xvI#r=NGXZZBep7G@-AJkPf)xh}3}D%zdi) zxFu<9axvP|IP87?(w%gsd*DcR57q#G*_YVi1{`-8kKRTS>TV1M)Pm<@@EgMnlg4@f zt1F*%v+sebz>gsrnt12xY|d@dI03t%B6p>p$HocRIaf_O$@Af?eZxHX?(1H6J;n?=c#aX;HOi#-gS9~coN=4?u zejnDA{_}J~9z8nJ*mGU}F;?wSZYi7MRebGOohr9eNC3fTfrJp9JlVq>0 znzFwn<1lOdl~uSlxkX>G#_-tok{j*e_PmM!gVZF+v3)8M_j`(O|c|Ib+Ju4 zhkl^K^Uw;Pd=IUz=u(4z_Ia<0e7-!#(uBnxb@FlG9^pDHN@kwVtf#Q=;dm-l5rS z=KhXXe+SrzfJ{C83^ni|03IrIiX&gCq&V=eW%!84hxe@TZXaP~=*;If*T36cNJz$Y zI1xDC3gE{-JbhdV+L?PQm%0(1Nj>~2BPVGJy=6R`H4C=MeI@PBd0G*0T^2FpwG5LgLSX!ufjT{4!SrYRX^u?ks)no%Vby+o?!p6PH6+-HtDb(I=TwIVX9IT-+GRTPC#zFd15t&lyi+&sH(iHy+5kfC7k+Y2n=myyx0KA-Wxm z$0rszyv*l}*>uLpwe0WLIlcPLFz#==Ke_?YhLbusyV6-xVkjNkmf(%|8Rovh6|mTk z+yo$y`KXMJxl%sYh6_Raj@OG3)xYfruFX=>Fdk&jLl64&s*tbBx4;z(GPY&zl8jB{ z9%1M&`GeT((bt22n(;o@#KQK#@e;eGP28@{Zl0~<9)|V*7{m7&L?56oI*7H?KLbZ4 zXMKF=uJ7e{yVVT;JVI`AHT*pdXjGINxAt`I(O$~XX4i2}g$j?!oYJ+aC^H{(@X32D zLh|vIKWds@uQF3(0s~EtKfLn;A$I_MVM0vns|+h}z_t6>5FHxbY?7WPS>>h^HiQ5+ zsZem@)>^EcTt1psB8mvD1EqnySpD(oDitrAI~kHOZrpm8#(oovew@)!BU%sUp9^XJ zWg+JPp?zE-vvXWoB5+uiBlGi=oNxXjS1N$(Q=5WovWspU!Ll;+u3_Rp*-}{HP=Rps zGcH6ipq67{`rYuWBtk~yiw*wpk^kjm`C+_b?1%mKRAI4YO9VUJ4kzf`l~;FS(;nO# z+%K9F^ou@Pez5}$eLuii>d(^^GQ8Wu8mAD{?|1uI>F!^{+*g?^$UzPTwCw#n^-cGI z&HZpUpegTXGa!}V`)5YWu@M6ug~myje|?a}FT0q0QkcXeZi(t&Z#8~Mz`=4dqOyLT zbX(dllfJ=os5_9dhk*JyK>y<%*fUvbjs@526V+#S9ScUJNwc_u$> zdqvYP>J_jUv0o~B(%B+zn+80MlR)6VDet0i-gnRy3*2FDlW#1F`?Qy!#zPEg(Drwn zn)MQ7l9nd^Y=OgzV^3To@}6#QwFSt|fsmlxaQA;$Y`*RIBjMib zTz~6FDXTa^0vtC+T_T`$%I{pu^?+WcAxm@e=eQ&Wg$k%W7Al_>l75R-zpY)vYqnJ# zEIw}(&YFhu%pD{=C}H(x1=JL2y|Y_$Je=a4PO6{yN<+V|R91Pp`5*F1|5;xB1W-v+W*A5NgbAxzf8Dq!#^iHxI;_gL@bTgYD;OXl*nwSE0?p6_m-du6rJ@fo>N^Yi$Y|RC;kToyk)LBSWkg8_Zt7+ zFD^sx_g@wQ#8eh${z3Y*W9XiLIIUa1$P?Jh_H!yT4TWLYU;T>zFMdV&VVMibm^WhF zKhmsZD-L>}7YjQFg!OsF?4KVA{9uRp)V}-I!X69L&woJ5`J?*!4u^blNCWgY3xDc7 z;JpL+qbkRfd>>^L$dw+-)P}sIzmaefKc%-8amnUTH>Qvrw_S$D{ZXu+)N~fiSU|lp z(2t+Z2)&pr_j{2ZBR*=ZA_4V%LLcj?{BvFxc(k=p|BMha&|dx{dnfZm5TOpWYeGN% z7$NivKdQ3?(WyKv)Z-~!Rn7`%Gk#8?J>yTLLd~ec$S`pV@`wLewKUXC3O)PZMS~Xh z`%eF{(=$~YS=Zw*72(2OTA2Uxf9w`$nSwfLkS_nt=KxHVXsqhtbljio(Onb~Wm^yK zi~xUgCgEta{x4SHetBX0t>`qrC<0s;0seN*S=M8#Gb46cI>KgPv2VQNGyG-Hr+?1* zUYqPUoj9cX+}Izv<>Neg=+ogjD|)PRr`YLa^IV?i8QWusyAAsE%^V1@aYs22o?e^l zaJj%BH?uvUPj7D%I+DWX*h)XXb_L}32Ub6f7oO0vPA*|E1!ST0LP(XTcCKI`T$BBkdeyY zNwr+pO)(%`HkLlz6cj$}nqBm#3sAK%$sfuz=dgr|*s>C&iCh!Y?@M^Q1t+6fv$KYX zl=~mr?mre)X-Sqr1@6D0yz_W3zG7crv5&PxoSQ%c{*H~SWISI9nblE+g>9a5k{5(J zm<)$~!LGA`C2(ua%UbCs7Ar0dPHmR?+?b~e=peggM>kGvKW6$X z9_n=KH%3}+VjYtLW&@hqAiDuq)j`SqGhhcdO<^jBcIuT?ylCuKHA5+d-ZI!=ntW3X zC9QG1QkF!vGX<4}#A#r@Nx9Zq8*R!9n$52WVj_niU>{yHW%#!iMEXbVg z_@WurPIYkYf2qP^7s21D$O1Q*m>}g35Ms4R;za7Z6|>u*fO$a{@;5E(4qe3z_2xw( zVoO&qYid@KuvK?{`%;y6Zi__lAi*Op<%V}L+)VrshwJt#SsRv?=f%bJWwsLdVE_q6@iDCcIAT=XvWv5hgY!|2-F;{oB)ciO1}7?Lsc z{RVhD_RbzwD4Thkv33X+{A;vBhr>9#3YV9oC7IhVyV;96YCnTL#ib_ zv;{=nX8!$YKRMF5wdgwyMC4BjBg1zlCUo!r@zZ&VC-lMJe;U{~@pcP;Vyw_%?dKg= zyo?!juLcg_k8mKbEMhQ^kA<;BctEUqCXJell^$rsYTHIdsKLMAqAI^n;Y(`KtnxyJqJ-X zL*ZlYP*~2E>YRzUxxJO@RjD6IK6YZ&uPqhN&-C5wyYT=JcW$fE`O4n%Ei~zvw?Zv+ z7RJS@_jjSl_kFe#Mk?!6nfNHdTa1+fcXEwNl+$g*r#RjEsq8`lAL`HPUP0k?mN*dA z6_51Z=MQEW*Y}(QY@yZ;)5dM*Ml!~51o-VoW8=961kTN_5=>hjk98DsTy-FEs4_0! zh28=x$z3njvTD(++IpY5Lq=S=S>U}&$@XmWy}8we&Vf6JYf_FsWKv5umvNqk!iY?c z#bIzFI&M;A_n52bG6@g${M2VJK{ZBwnKdqNENrVf1HT$3PtimqDBH_lln>>Nnq%5V zx{O#Rmg4)zb0Uagbp}xpbsWdgwjN1k!`4*eiR=mG0`IKm=}BG*xyMenU<{;3A50zj z6^q_={P=D`$7tb3mU@Ugamx0?h*Zfv$Lw83LP0q)RxJ||as=B!Q(BCWsg$E=8gJRW z^++#pl6iK~nPSGC!6^|$<`hz<|55LY$qeI4Uzu$V9r>cF5*HH%ogAa742w=t-Cxn- z>LJmrpSSsls2Xpzj?6*1sFKIuh_@WlZKUmQ<3_aG5#_u<{|Mrewj3bS zo;4K?IPW^M4?Eo*@87e1+vwGXlBZHbZ%ICAHKOLGkhI`w?1GMcosV%=Wm7dJ{z<>W z}ZcNc7fBka+RPOTx+rMUS%Ok z`nJ84-Ld$)1Uo*cscZO`>Vm$;4v%h5qOL^ju`~B8$DXH$X>t4Pev_wcc%Jff^Fe|S z_J5F?jUK&jRnDUe=7bC@aam9CM1zMfR_=)B07rBaJMb)uLm? zR!LhvGC{qN3azCaTu**IAsy04l|ZTY9sZQeq+t>=YtPl06O^oS!#!`-+7+)3^sHer zV|D4JVa1~!g2Wky15Q`!@4*=vSOL4~{Uoy>rFptXo>qH4>Abl4uY7BBvp8}-;0T>G zdf8L;$s?yy>oqJtn3>N<2f!&ovNr+(#^0SX@aOzOX|bka7whBh>WflOD5+~^4ljlL z5z+ru{I^LEv8^zpamoZ&p>YeGX{{YSVd7;_-G*`%n{b2rZio)lr= z8HUKjcZIkD#b9W#?`c0F;{WlG`Iv>A%O?tX$iF`YiQRw2^#7Vl(0%DneBM%W{mjKJ zCwX5e)h3Pl9S}}=sd$sc(8_C?k*2Mkr=@cSG>>YY-fZ4WANAlH^bor91XCLheHNXC zaOn}nq4z~m9(W17<3PY@?Z=4xc{n$pGM__Cg*6Uw2-yOxT+Vd59e+B-H0vQ~AErg@+m_{zT$A9lSoB z2N?brLL`IkGAX2y82*Hv@#N@b24*BK;Zvxu?We{lK_MY@j9x8lGp;$quLIYwR7582 z>wCwW{R#?Xfi*4a9C?~tZRE=rOFoJiUPnOj2;!l0a z1cjEzimP0QKS6!S;UouQ$mV5lu}vIaBqs@rQ$7@mWrp1N(7cX7+1W;Py_=gAwb+Yv zW~#38bzUe#S?+j^duvbWEwU|R*RJFd!x0LlCw!;V0JnVJuSEPJw6D7GG^y6$mJCNl zoD@0uxxOqhYvjoue(qx_7SAQhCo5qp^q~0S@sPUCkWV#EzvD=r+*y5JLgkCyQ>Uuy zpE%*^79IRS!&M@WBEOnu#!7Lxw%_g}`=ny~yzgU`HRyy9gZ{+`R!tLC#zKKqBUnD3 zvs0PF!1doF^mjg1p)&h-Zz_S!_k@nD(`HEL9;Z zjI3PMoY$7MkZidy@e3M%N&)?^Rga|pu7>61jSK|yKtL?W*h7E8s`6~?3`cJEw+0OW zK6z5ZEpUVGpu!}{3yIOm5uN-{O{;fK1gB|PSao;tVIt)eW`#^=W7X7!-A-RzXO(<; ze2rzLtGDh9=4L5R+B>|xmpoML`WOFXBKH^;8=WQ@d+fHS!KIP+$`r8ayxujS1MTk_ z>4oHrfBjE+9nc-Q2G+Ef);&RwIfopjp?E!>N`_UbQlD&OsvgOxRrZ%VDLVCHBKQS> zz6M!SH=EF7xFI)FF}uLYiknJKymPx92!*rCzT><4I6NPvL*=|RdApVzr-!=g6(nC_ z+RHCi0sXg2A9G6USd3DvX=K0lbm?lh!1@NZ$NhOcJePGACW}BTr5_0TFk^>;+mC%! zwxO%~A8P~rH2$(+j18KxPZXpyo#=SQ38NHW06jeX9!aa56MNmp&R4P7_+W!EeLhL% z)Ip_t4pc|3k*^ciUNhg@M*LW3Kf68rtHcXEz{NJr4@(8Zs>t3~E`NhNhiyq+j0pZ@ zpea4+ZxM&mShf{V^zHozT_eUMuO({6CiXG_LEmaxcY4Kzb~3A;`gnr_e(6eBcZou| zA?OlvB$2p24)p7E0Ho$0}xt4X~XyNo^%Ny$4Q;c`}X1dm!4 zj6kcpMXmexWXI`H1FZWj5J{(Rce*NZn%f&xbDQ~o&FcM$;{lw zs^w_OU%tujeIG`?DR#VBzhNkB^a zjHA6nge20GoYoh_O@mHKkmIIT;?Bsii2QjNwMjdG7&%RxpKH4eN@Fxt>Pz+3TMB-4 z@9a?aC0j6{0gw37C?#Zjo@fQT(;Cmq)H&u2%A*62Xrabj_+!n?g)72mtkF4 zpGXi?kS5r(2Z6+T?M%EuHMg>WnQj@)K3wv$eee<*Rs3np2lmcE@MhRpDJ_OQG_^SF`rI7HIP`-?k7tX{!kD4 zA-~wQEUW=y@q5u1Cc+Jr-do5G*82{ni5x+53;HyO;?A_S_vaYads2h^`2q@(C|tNd zSU9MUH;X$jWOqQ~u4+H;oVIXf7r@G();y#L^?QZ|WVwkaXUd0or&1o5tSEWaS`}D1Uw^q2(NODtRf7jsYE0s-SyFxQs7C7!T6ni8bRl-FLyq*Sx%l2`2nE zBZatkFTYsjvy9ULUe)nmj&tfa@a zi13MS{hmyMY6Z1LhWLblUODjYBV}3Hi=ZJOaFHC4m(Wjxz9-={Du zz@H}}D^GSTCi$JpQbP=8NIyrREY`(Dzc1Lk2d)jXR5)-aMODKTwnyu3M(H-Oy>MyK z82kJuN2L-jB#Hg?i|eYVbD6F^WVVZ%A>L z-KaM=kBk*1k{M7c?&>Js!GhMNDf$fJmq5S`baMzK;R9Lsi@|}84H9PX zf$u+mqPC_eovLN(Aetf+Cx$Y53cszlX>nR&T6@7-a) zfwVWr`KOUJ%b<}5#MZ(B>gOeBpZZ&8Q^3j%a;EVU^J08<)%(f8p1UUaP_vs^oLkTh47qlg`Wnu`4fDfHdPxHj;=MtBdJx6 z9={)6dnH~oH-f{QU>I}nZ!r4Zp@Geu7>^>CZ!r>lS+RHd%9sXlwB-G64;4Xs@dRa! z-^y*6JO;Jne0?ZCVrD0k%PjBa#&>nIjSfnn)V{?tsKMhb%yc0xa^q|~gTQ&WjfTDA z`WM4bAx=eEtXF+?U|*^+_gHLS`_ zHM{T!uY6Mzy~5O36S^)%yS1uFHQ#L0j1j%q%cxlfMK2Ddd!2>{4hMt|rMQ|lmb#Wk zt2T_zaR3pK^V&u3eLmmJU?z09R1p^Re2*l7gH1*_q48bS{owgHnEuM57GzZsKBPJeEO z4p1*?TX|$JpF17dR-f0!UWt>(>Q5RG*#yRRwdr`mhP*9nuSb$Ja*h48(@&WzUuMp% zsO!IzCpAZ6KuL&`Xxhj#Q|)*VlhP7WAHQipe_L8(Q{h>~tD7qgz&t~&aY$*qWmi&8 zR}l6s%$I#tbx>`kMF$dcUw#`_**%N5S1ajTX`Hap?Ulcp;KB8(ofhbQ?)d25`gy_x zfMIx#I70rg!OSF;-FI+<%T*cN^0F;|7DWC1*ly`cVb>%O#j}p;DOGi~q#jGf=j_Z6G?31}T4WDCMgbzMWAurP|Qf*DbNIBFi6)PDJF!%2+v5VOJ!0 z^dCPcAb^Kva2GLzFls@VbAi}FsM0ikVLB$;elS4*Mj?hdkB&rM|2q&6-@l?0>d^l} z8G=r?E)lkJf-hF>aRCVXyKgz&raMUCQW#00AAeS9_`f>)uNBUFrvH5o?6?|iW+V!8 ze(ExzE?}zD2~C*|Ph!uc>w?>=f?v^O7hEo9he~WMe@gTG%jhi;L)ov*m#_3{0AWt_V)%X;NBNU^rvthx?Nv$F1qNCL@x@t+&cmt%o_aL{Cg1tR>*jsza8m3~6^xX#T<5J7I$E1>m z%`=z@o{|)5;Ch2Al^bt4a2^o<`Cay(@aAV~*L=FFyz*cJdprG6YU7>W;yv*IbskJy zY4+Pb7qw;G8UFd`;=%W0jzs6RxM}Tr%CbY_nYuGKm=$K)Gfx2$iZ`WCc>Ecdjc;EF zU(XaWrBK8xkM8xOPCJRDelmB|UaOX%JsXb;ZOOQvXinekd2Y8=peMj+k*{RT&V|27 zWd_V}!NaC`uK1E4&w2gC4W3r|vR?k;RhY@R?UvA%Z0$rw&S3z1s05PSO^Tf`BeHrSKZW#)T+=aw|AG$uPVHGoD-S{p?k2axbx zQ;T|HNMUwF+idT0LnKZSLSnmrL~r2}$aUXFXUGH1^4x(K1v57*ZKJ8f-T`Nj(y?yO zbkNUTYd0T2OSmoDHU1qMevkcdAP1d+#=KWHCOQw|uSDSx#E_cooh#2axT-yE)C_9q zia$8uA%EOa9d@O+eUSSImue^0q?zTWDvi5^EwtOM4^TGaz(VlX?FuxtFjJhFF!wVI zWJ}7=Ow?YrumY#em37aQ{l`W~Tv9g-{tRY`soq{6=+*GL|A6JqqSbjQ3Ytn>bAg%}B0oHlH=Xeb&Ai z(ppaWXLE?tG$9)-+fOl1@Mc7bQ@vn`oZbj{;L-J>sqsc?M)DW0 zSGicAi}U{;{h@p7#1Lz;VWw)c@mC5j?hz?_;p4RM;S|abNbZ7sEqvX%6*MAe9odJ{eztRBv0qEzQ@xTB1e Date: Thu, 17 May 2018 18:26:49 -0300 Subject: [PATCH 22/32] [DOC] updating navigation diagram link in ghpages --- .../Fluxograma_nav.png} | Bin docs/UX/index.md | 3 +++ docs/index.html | 22 ++++++++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) rename docs/{images/Diagrams/Diagrama_de_UX.png => UX/Fluxograma_nav.png} (100%) create mode 100644 docs/UX/index.md diff --git a/docs/images/Diagrams/Diagrama_de_UX.png b/docs/UX/Fluxograma_nav.png similarity index 100% rename from docs/images/Diagrams/Diagrama_de_UX.png rename to docs/UX/Fluxograma_nav.png diff --git a/docs/UX/index.md b/docs/UX/index.md new file mode 100644 index 0000000..a5a5822 --- /dev/null +++ b/docs/UX/index.md @@ -0,0 +1,3 @@ +# Fluxograma de Interação/Navegação + + diff --git a/docs/index.html b/docs/index.html index 963a17d..a0200bc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -184,6 +184,24 @@ +
  • + +
    • @@ -450,7 +468,7 @@

      Projetos

      - Crie e compartilhe os dados da sua pesquisa. Escolha os dados que deseja disponibilizar, + Crie e compartilhe os dados da sua pesquisa. Escolha os dados que deseja disponibilizar, interprete as informações, gere indicadores e gráficos, crie e gerencie os seus observatórios.

      @@ -460,7 +478,7 @@

      Colaboradores

      - Trabalhe em equipe, interaja com os dados das pesquisas, + Trabalhe em equipe, interaja com os dados das pesquisas, gere seus próprios indicadores e contribua com o conhecimento do projeto.

      From 80326ec913e9e066ddcfe6b5e10c067d05ac6107 Mon Sep 17 00:00:00 2001 From: jppgomes Date: Thu, 17 May 2018 18:40:08 -0300 Subject: [PATCH 23/32] [FIX] fix desnecessary prints --- import_data/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 86b9990..30e4ac2 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -23,9 +23,7 @@ class FileUploadView(APIView): def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] - print(request.data) list_fields = request.POST.getlist('headers') - print(list_fields) serializer = ImportDataSerializer(data=request.data) if serializer.is_valid(): file_path = '/code/tmp/' + file_obj.name @@ -39,7 +37,6 @@ def post(self, request, format=None): dataframe = pandas.read_csv(file_path, header=0) for field in list_fields: - print(field) dataframe = dataframe.drop(field, axis=1) json_data = json.loads(dataframe.to_json(orient="records")) From dad2fe42ce57f2c31227297be9029f337f2e06cc Mon Sep 17 00:00:00 2001 From: andrebargas Date: Thu, 17 May 2018 19:51:07 -0300 Subject: [PATCH 24/32] [ADD] adding data type validation Co-authored-by: joao pedro --- import_data/views.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 30e4ac2..1050b6d 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -23,7 +23,9 @@ class FileUploadView(APIView): def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] - list_fields = request.POST.getlist('headers') + to_remove_list_fields = request.POST.getlist('headers') + to_define_list_fields = request.POST.getlist('define') + type_list_fields = request.POST.getlist('types') serializer = ImportDataSerializer(data=request.data) if serializer.is_valid(): file_path = '/code/tmp/' + file_obj.name @@ -36,8 +38,13 @@ def post(self, request, format=None): dest.write(chunk) dataframe = pandas.read_csv(file_path, header=0) - for field in list_fields: - dataframe = dataframe.drop(field, axis=1) + + dataframe = dataframe.drop(to_remove_list_fields, axis=1) + + for dfield in to_define_list_fields: + for type in type_list_fields: + dataframe[dfield] = dataframe[dfield].astype(type) + json_data = json.loads(dataframe.to_json(orient="records")) mongo_client = pymongo.MongoClient('mongo', 27017) From 2f4dfd297b2a57a6b667a37485208c414e66b5cf Mon Sep 17 00:00:00 2001 From: andrebargas Date: Thu, 17 May 2018 20:18:46 -0300 Subject: [PATCH 25/32] [ADD] adding HTTP response to field data definition Co-authored-by: pedro daniel Co-authored-by: joao pedro Co-authored-by: yoshida --- import_data/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 1050b6d..1f56c18 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -1,7 +1,5 @@ import pandas import pymongo -# from flask import jsonify, json -# from bson import json_util import json import os from django.core.files.storage import default_storage @@ -43,7 +41,12 @@ def post(self, request, format=None): for dfield in to_define_list_fields: for type in type_list_fields: - dataframe[dfield] = dataframe[dfield].astype(type) + try: + dataframe[dfield] = dataframe[dfield].astype(type) + except ValueError: + return Response(serializer.errors, + status=status. + HTTP_400_BAD_REQUEST) json_data = json.loads(dataframe.to_json(orient="records")) From 93f9727c03b7a2b19277cceb028a4632f01ca7ad Mon Sep 17 00:00:00 2001 From: andrebargas Date: Fri, 18 May 2018 11:17:14 -0300 Subject: [PATCH 26/32] [FIX] fixing validation interator to set fields types Co-authored-by: joao pedro --- import_data/views.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 1f56c18..e95a7d7 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -39,15 +39,16 @@ def post(self, request, format=None): dataframe = dataframe.drop(to_remove_list_fields, axis=1) - for dfield in to_define_list_fields: - for type in type_list_fields: - try: - dataframe[dfield] = dataframe[dfield].astype(type) - except ValueError: - return Response(serializer.errors, - status=status. - HTTP_400_BAD_REQUEST) - + for dfield, type in zip(to_define_list_fields, + type_list_fields): + try: + dataframe[dfield] = dataframe[dfield].astype(type) + break + except ValueError: + return Response(serializer.errors, + status=status. + HTTP_400_BAD_REQUEST) + print(dataframe.dtypes) json_data = json.loads(dataframe.to_json(orient="records")) mongo_client = pymongo.MongoClient('mongo', 27017) From 1bebdfb86bbcc3500606ee713a5a0d4f1c183aaa Mon Sep 17 00:00:00 2001 From: Yoshida-Eduardo Date: Sat, 19 May 2018 01:20:17 -0300 Subject: [PATCH 27/32] [UPDATE] minor to import post Changed due to problems with formAppend on frontend Co-authored-by: Andre --- import_data/views.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index e95a7d7..83a0a3c 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -21,9 +21,13 @@ class FileUploadView(APIView): def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] - to_remove_list_fields = request.POST.getlist('headers') - to_define_list_fields = request.POST.getlist('define') - type_list_fields = request.POST.getlist('types') + # Fix temporária por probelmas com forma aappend + to_remove_list_fields = json.loads(request.data['headers']) + to_define_list_fields = json.loads(request.data['define']) + # Problemas na passagem de tipo de dado como string + # type_list_fields = json.loads(request.data['types']) + type_list_fields = [] + serializer = ImportDataSerializer(data=request.data) if serializer.is_valid(): file_path = '/code/tmp/' + file_obj.name From 7355314c50da473d8a5a6a5e1589248b841de683 Mon Sep 17 00:00:00 2001 From: IagoCarvalho Date: Sat, 19 May 2018 11:20:36 -0300 Subject: [PATCH 28/32] [DOC] updating usability tests planning Co-authored-by: Renata --- .../project_artefacts/usability_test/index.md | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/project_artefacts/usability_test/index.md b/docs/project_artefacts/usability_test/index.md index bd1e228..ac8da83 100644 --- a/docs/project_artefacts/usability_test/index.md +++ b/docs/project_artefacts/usability_test/index.md @@ -1,8 +1,17 @@ -# Testes de Usabilidade -## Introdução -Um teste de usabilidade tem como principal objetivo observar e avaliar o uso da -aplicação por parte dos usuários reais, visando encontrar problemas e pontos de -melhorias. +# Planejamento dos Testes de Usabilidade + +### Objetivo +O principal objetivo do Teste de Usabilidade do sistema Observ é determinar as respostas emocionais, feedback, tempo, passos e número de erros resultantes da interação do usuário com a aplicação. Ao final dos testes a equipe visa encontrar problemas e oportunidades de melhorias. + +### Perfil de usuário +* Usuários que tenham experiência no dia a dia com websites. +* Usuários com experiência em aplicações para manipulação de dados. + +### Ambiente +* Notebook + +### Regras +Duração de 10 minutos. ## Tópicos de Análise Para os testes de usabilidade que serão usados no projeto, serão avaliados os seguintes tópicos: @@ -60,6 +69,11 @@ Para os testes de usabilidade que serão usados no projeto, serão avaliados os + + Número de erros cometidos + + + @@ -112,4 +126,5 @@ Esse teste foi idealizado baseado na ideia de Richard Littauer, o artigo do mesm ## Referências -- The User Has Sobered Up - Littauer, Richard https://medium.com/@richlitt/the-user-has-sobered-up-df0b411997ea +- [The User Has Sobered Up - Littauer, Richard](https://medium.com/@richlitt/the-user-has-sobered-up-df0b411997ea) +- [Testes de Usabilidade](https://www.caelum.com.br/apostila-ux-usabilidade-mobile-web/usabilidade/#o-que--medido) From c61f02320e0795baff3ad8071e455bfd772fa59a Mon Sep 17 00:00:00 2001 From: Renatafsouza Date: Sat, 19 May 2018 11:46:13 -0300 Subject: [PATCH 29/32] [DOC] Updating usability_test Co-authored-by: Iago --- .../project_artefacts/usability_test/index.md | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/project_artefacts/usability_test/index.md b/docs/project_artefacts/usability_test/index.md index ac8da83..bda408f 100644 --- a/docs/project_artefacts/usability_test/index.md +++ b/docs/project_artefacts/usability_test/index.md @@ -8,7 +8,7 @@ O principal objetivo do Teste de Usabilidade do sistema Observ é determinar as * Usuários com experiência em aplicações para manipulação de dados. ### Ambiente -* Notebook +* Notebook com a ferramenta SimpleScreenRecorder instalado; ### Regras Duração de 10 minutos. @@ -80,12 +80,20 @@ Para os testes de usabilidade que serão usados no projeto, serão avaliados os ## Testes Utilizados Aqui estão definidos os testes de usabilidade que serão aplicados pela equipe. É importante resaltar que existem testes que cobrem vários dos tópicos de análise. +Antes de iniciar os testes, deve-se passar algumas informações ao usuário: +* Informar que quem será testado é o sistema e não o usuário. +* Pedir para o usuário falar em voz alta o que ele está pensando. +* Será utilizada a ferramenta de captura de tela "SimpleScreenRecorder" para acompanhar os passos de navegação do usuário pelo sistema Observ. + ### Teste 01 - Teste de Workflow #### Objetivo Avaliar a implementação da interface do sistema ao ser utilizada pelo usuário. +#### Duração +* Máximo de 5 minutos. + #### Roteiro do Teste 1. O usuário estando na página principal, pesquisa pelo dashboard x. @@ -109,6 +117,9 @@ Avaliar a navegação da interface, considerando que os usuários estarão sob c #### Justificativa Esse teste foi idealizado baseado na ideia de Richard Littauer, o artigo do mesmo se encontra nas referências do documento. +#### Duração +* Máximo de 5 minutos. + #### Roteiro do Teste 1. O usuário estando na página principal, pesquisa pelo dashboard x. @@ -116,13 +127,30 @@ Esse teste foi idealizado baseado na ideia de Richard Littauer, o artigo do mesm 3. O usuário confere os dados e baixa os dados do dashboard. 4. O usuário volta para a página principal. 5. O usuário realiza cadastro. -6. O usuário cria um projeto. -7. O usuário faz upload de um set de dados. -8. O usuário cria um dashboard. -9. O usuário entra no dashboard. -11. O usuário baixa os dados do dashboard. +6. O usuário cria um projeto com tags. +7. O usuário faz upload de um set de dados com extensão .csv. +8. O usuário parametriza os dados. +9. O usuário cria um dashboard. +12. O usuário entra no dashboard. +13. O usuário baixa os dados do dashboard. --- +## Teste 03 - Teste de Workflow - Gerar indicadores + +#### Duração +* Máximo de 5 minutos. + +#### Roteiro do Teste + +* Meta da tarefa: Gerar indicadores. + +1. O usuário realiza cadastro. +2. O usuário realiza o login. +2. O usuário seleciona um projeto. +3. O usuário seleciona um dataset. +4. O usuário seleciona os dados. +6. O usuário gera um gráfico com indicadores. + ## Referências From 19b06562b0222a8dff94720772679b8051e8169a Mon Sep 17 00:00:00 2001 From: andrebargas Date: Thu, 17 May 2018 21:31:43 -0300 Subject: [PATCH 30/32] [FIX] Fixing Travis build errors --- projects/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/projects/views.py b/projects/views.py index ef1b0e3..ece2383 100644 --- a/projects/views.py +++ b/projects/views.py @@ -16,8 +16,6 @@ from rest_framework_jwt.authentication import JSONWebTokenAuthentication - - @permission_classes((IsAuthenticatedOrReadOnly,)) class ProjectList(generics.ListAPIView): authentication_classes = (JSONWebTokenAuthentication, @@ -29,7 +27,6 @@ class ProjectList(generics.ListAPIView): def get(self, request, format=None): tag_name = self.request.query_params.get('tag_name', None) - print(tag_name) if tag_name is None: projects = Project.objects.all() else: From 6362d7f2204086f643ba177eee2d47ef2aebe105 Mon Sep 17 00:00:00 2001 From: Yoshida-Eduardo Date: Sat, 19 May 2018 13:40:35 -0300 Subject: [PATCH 31/32] [FIX] fixed list to define types Co-authored-by: Andre --- import_data/views.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/import_data/views.py b/import_data/views.py index 83a0a3c..c36e15b 100644 --- a/import_data/views.py +++ b/import_data/views.py @@ -21,12 +21,10 @@ class FileUploadView(APIView): def post(self, request, format=None): file_obj = request.data['file'] project_id = request.data['project'] - # Fix temporária por probelmas com forma aappend + # Fix temporária por probelmas com forma append to_remove_list_fields = json.loads(request.data['headers']) to_define_list_fields = json.loads(request.data['define']) - # Problemas na passagem de tipo de dado como string - # type_list_fields = json.loads(request.data['types']) - type_list_fields = [] + type_list_fields = json.loads(request.data['types']) serializer = ImportDataSerializer(data=request.data) if serializer.is_valid(): @@ -52,7 +50,6 @@ def post(self, request, format=None): return Response(serializer.errors, status=status. HTTP_400_BAD_REQUEST) - print(dataframe.dtypes) json_data = json.loads(dataframe.to_json(orient="records")) mongo_client = pymongo.MongoClient('mongo', 27017) @@ -80,7 +77,6 @@ def get(self, request, pk, format=None): elements = collection.find({}) json_docs = [] for doc in elements: - print(type(doc)) # Remove o campo _id do elemento, pois ele não é serializável del(doc['_id']) json_docs.append(doc) From f7eee5dda94f611942d5f2c8f9a6e90c5ca6b29c Mon Sep 17 00:00:00 2001 From: IagoCarvalho Date: Sat, 19 May 2018 14:28:43 -0300 Subject: [PATCH 32/32] [DOC] updating usability doc on ghpages --- docs/index.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/index.html b/docs/index.html index a0200bc..abfcb25 100644 --- a/docs/index.html +++ b/docs/index.html @@ -197,6 +197,11 @@ Fluxograma de Navegação
    • +
    • + + Planejamento dos testes de Usabilidade + +