From aa0a100a858af3b1e35884d72b77f0af5a9e0ed3 Mon Sep 17 00:00:00 2001 From: SunDean <1264174498@qq.com> Date: Sat, 5 Aug 2023 22:17:56 +0800 Subject: [PATCH] [improvement][project] supersonic 0.7.0 version backend update (#24) * [improvement][project] supersonic 0.7.0 version backend update * [improvement][project] supersonic 0.7.0 version backend update * [improvement][project] supersonic 0.7.0 version readme update --------- Co-authored-by: jolunoluo --- .../authentication/adaptor/UserAdaptor.java | 25 +++ .../api/authentication/pojo/Organization.java | 23 +++ .../authentication/service/UserService.java | 8 + .../DefaultUserAdaptor.java} | 55 +++--- .../interceptor/AuthenticationIgnore.java | 2 +- .../AuthenticationInterceptor.java | 6 +- .../DefaultAuthenticationInterceptor.java | 6 +- .../interceptor/InterceptorFactory.java | 2 +- .../dataobject/UserDO.java | 2 +- .../dataobject/UserDOExample.java | 2 +- .../mapper/UserDOMapper.java | 6 +- .../repository/Impl}/UserRepositoryImpl.java | 10 +- .../repository/UserRepository.java | 4 +- .../authentication/rest/UserController.java | 24 ++- .../service/UserServiceImpl.java | 52 +++++ .../strategy/FakeUserStrategy.java | 2 +- .../strategy/HttpHeaderUserStrategy.java | 4 +- .../strategy/UserStrategyFactory.java | 2 +- .../utils/ComponentFactory.java | 23 +++ .../{domain => }/utils/UserTokenUtils.java | 2 +- .../main/resources/mapper/UserDOMapper.xml | 16 +- .../application/AuthServiceImpl.java | 21 +- .../chat/api/component/SemanticQuery.java | 2 + .../chat/api/pojo/DomainSchema.java | 9 +- .../chat/api/pojo/QueryContext.java | 6 +- .../chat/api/pojo/SchemaElement.java | 10 +- .../chat/api/pojo/SchemaElementMatch.java | 6 + .../chat/api/pojo/SemanticParseInfo.java | 37 ++-- .../chat/api/pojo/SemanticSchema.java | 2 +- .../api/pojo/request/ChatAggConfigReq.java} | 8 +- .../api/pojo/request}/ChatConfigBaseReq.java | 9 +- .../pojo/request}/ChatConfigEditReqReq.java | 2 +- .../api/pojo/request}/ChatConfigFilter.java | 2 +- .../pojo/request/ChatDefaultConfigReq.java} | 15 +- .../pojo/request/ChatDetailConfigReq.java} | 13 +- .../chat/api/pojo/request}/Entity.java | 2 +- .../api/pojo/request/ExecuteQueryReq.java | 15 ++ .../api/pojo/request}/ItemVisibility.java | 2 +- .../request}/KnowledgeAdvancedConfig.java | 2 +- .../api/pojo/request/KnowledgeInfoReq.java} | 4 +- .../chat/api/pojo/request/PluginQueryReq.java | 7 +- ...ueryDataRequest.java => QueryDataReq.java} | 2 +- .../{QueryRequest.java => QueryReq.java} | 3 +- ...stion.java => RecommendedQuestionReq.java} | 6 +- .../pojo/response/ChatAggRichConfigResp.java | 26 +++ .../api/pojo/response}/ChatConfigResp.java | 12 +- .../pojo/response/ChatConfigRichResp.java} | 12 +- .../response/ChatDefaultRichConfigResp.java} | 7 +- .../response/ChatDetailRichConfigResp.java | 27 +++ .../pojo/response/EntityRichInfoResp.java} | 4 +- .../pojo/response}/ItemVisibilityInfo.java | 2 +- .../chat/api/pojo/response/MetricInfo.java | 1 + .../chat/api/pojo/response/ParseResp.java | 25 +++ .../{QueryResponse.java => QueryResp.java} | 2 +- ...estion.java => RecommendQuestionResp.java} | 6 +- ...ommendResponse.java => RecommendResp.java} | 2 +- .../{SearchResponse.java => SearchResp.java} | 4 +- chat/core/pom.xml | 9 +- .../chat/config/AggregatorConfig.java | 13 ++ .../chat/config/ChatAggRichConfig.java | 24 --- .../supersonic/chat/config/ChatConfig.java | 10 +- .../chat/config/ChatDetailRichConfig.java | 30 --- ...onfig.java => FunctionCallInfoConfig.java} | 2 +- .../supersonic/chat/mapper/EntityMapper.java | 69 +++++++ .../chat/mapper/FuzzyNameMapper.java | 10 +- .../chat/mapper/HanlpDictMapper.java | 4 + .../chat/mapper/QueryFilterMapper.java | 60 +++++- .../chat/parser/SatisfactionChecker.java | 45 +++-- .../embedding/EmbeddingBasedParser.java | 165 ++++++++++------ .../embedding/EmbeddingEntityResolver.java | 16 -- .../chat/parser/function/DomainResolver.java | 3 +- .../parser/function/FunctionBasedParser.java | 114 +++++++++-- .../chat/parser/function/FunctionFiled.java | 13 ++ .../chat/parser/function/FunctionReq.java | 4 +- .../function/HeuristicDomainResolver.java | 43 ++-- .../chat/parser/function/Parameters.java | 17 ++ .../parser/llm/LLMTimeEnhancementParse.java | 49 +++++ .../chat/parser/rule/AggregateTypeParser.java | 20 +- .../parser/rule/ContextInheritParser.java | 56 ++++-- .../chat/parser/rule/QueryModeParser.java | 1 - .../chat/parser/rule/TimeRangeParser.java | 10 +- .../chat/persistence/dataobject/PluginDO.java | 42 ++++ .../persistence/mapper/PluginDOMapper.java | 1 - .../repository/ChatConfigRepository.java | 4 +- .../repository/ChatQueryRepository.java | 8 +- .../impl/ChatConfigRepositoryImpl.java | 4 +- .../impl/ChatQueryRepositoryImpl.java | 22 +-- .../supersonic/chat/plugin/Plugin.java | 41 +++- .../supersonic/chat/plugin/PluginManager.java | 51 ++--- .../chat/plugin/PluginParseConfig.java | 21 ++ .../chat/plugin/PluginParseResult.java | 5 +- .../chat/query/HeuristicQuerySelector.java | 69 ++++--- .../supersonic/chat/query/QueryManager.java | 33 +++- .../supersonic/chat/query/QuerySelector.java | 2 +- .../chat/query/plugin/ParamOption.java | 37 ++++ .../supersonic/chat/query/plugin/WebBase.java | 14 +- .../chat/query/plugin/WebBaseResult.java | 14 ++ .../chat/query/plugin/dsl/DSLQuery.java | 3 +- .../query/plugin/webpage/WebPageQuery.java | 71 ++++--- .../query/plugin/webpage/WebPageResponse.java | 5 +- .../plugin/webservice/WebServiceQuery.java | 46 ++--- .../chat/query/rule/QueryMatcher.java | 35 +--- .../chat/query/rule/RuleSemanticQuery.java | 83 +++++--- .../query/rule/entity/EntityDetailQuery.java | 2 +- .../query/rule/entity/EntityFilterQuery.java | 5 +- .../query/rule/entity/EntityListQuery.java | 24 ++- .../rule/entity/EntitySemanticQuery.java | 12 +- .../query/rule/entity/EntityTopNQuery.java | 25 --- .../query/rule/metric/MetricDomainQuery.java | 26 +-- .../query/rule/metric/MetricEntityQuery.java | 92 +++++++++ .../query/rule/metric/MetricFilterQuery.java | 31 ++- .../rule/metric/MetricSemanticQuery.java | 57 ++++-- .../query/rule/metric/MetricTopNQuery.java | 2 +- .../chat/rest/ChatConfigController.java | 10 +- .../supersonic/chat/rest/ChatController.java | 10 +- .../chat/rest/ChatQueryController.java | 27 ++- .../chat/rest/PluginController.java | 2 +- .../chat/rest/RecommendController.java | 32 +-- .../supersonic/chat/service/ChatService.java | 8 +- .../chat/service/ConfigService.java | 10 +- .../supersonic/chat/service/QueryService.java | 17 +- .../chat/service/RecommendService.java | 12 +- .../chat/service/SearchService.java | 4 +- .../chat/service/SemanticService.java | 141 +++++++++----- .../chat/service/impl/ChatServiceImpl.java | 18 +- .../chat/service/impl/ConfigServiceImpl.java | 37 ++-- .../chat/service/impl/PluginServiceImpl.java | 49 +++-- .../chat/service/impl/QueryServiceImpl.java | 101 +++++++++- .../service/impl/RecommendServiceImpl.java | 30 +-- .../chat/service/impl/SearchServiceImpl.java | 8 +- .../supersonic/chat/utils/CacheUtils.java | 6 +- .../chat/utils/ChatConfigHelper.java | 17 +- .../supersonic/chat/utils/ChatGptHelper.java | 78 ++++++++ .../supersonic/chat/utils/DictMetaHelper.java | 14 +- .../chat/utils/DictQueryHelper.java | 4 +- .../chat/utils/QueryReqBuilder.java | 92 ++++++--- .../main/resources/mapper/PluginDOMapper.xml | 32 ++- .../parser/TimeRangeParserTest.java | 4 +- .../chat/mapper/HanlpDictMapperTest.java | 4 +- .../test/context/MockBeansConfiguration.java | 7 +- .../context/SemanticParseObjectHelper.java | 2 +- .../dictionary/builder/EntityWordBuilder.java | 20 +- .../knowledge/semantic/BaseSemanticLayer.java | 32 --- .../semantic/DomainSchemaBuilder.java | 27 +-- .../semantic/LocalSemanticLayer.java | 6 - .../semantic/RemoteSemanticLayer.java | 6 - .../supersonic/common/pojo/DateConf.java | 30 ++- .../common/pojo/enums/RatioOverType.java | 4 +- docs/images/supersonic_components.png | Bin 308039 -> 194643 bytes .../main/resources/META-INF/spring.factories | 10 +- .../src/main/resources/db/chat-data-h2.sql | 3 - .../src/main/resources/db/chat-schema-h2.sql | 2 + .../main/resources/META-INF/spring.factories | 8 +- .../com/tencent/supersonic/ConfigureDemo.java | 183 +++++++++++++++--- .../main/resources/META-INF/spring.factories | 10 +- .../src/main/resources/db/data-h2.sql | 17 +- .../src/main/resources/db/schema-h2.sql | 36 +--- .../supersonic/integration/BaseQueryTest.java | 64 +++--- .../integration/EntityQueryTest.java | 43 +++- .../integration/MetricQueryTest.java | 24 +-- .../integration/MultiTurnsTest.java | 12 +- .../integration/plugin/BasePluginTest.java | 8 +- .../plugin/PluginRecognizeTest.java | 7 +- .../tencent/supersonic/util/DataUtils.java | 28 ++- .../test/resources/META-INF/spring.factories | 4 +- .../src/test/resources/db/data-h2.sql | 23 +-- .../src/test/resources/db/schema-h2.sql | 2 + .../api/model/request/PageSchemaItemReq.java | 3 +- .../api/model/response/DomainSchemaResp.java | 2 +- .../semantic/api/query/request/MetricReq.java | 1 + .../application/DimensionServiceImpl.java | 1 + .../model/application/DomainServiceImpl.java | 56 ++++-- .../model/application/MetricServiceImpl.java | 3 + .../semantic/model/domain/DomainService.java | 3 + .../model/domain/pojo/MetaFilter.java | 3 +- .../mapper/MetricDOCustomMapper.java | 3 + .../repository/DimensionRepositoryImpl.java | 5 +- .../repository/MetricRepositoryImpl.java | 28 +-- .../mapper/custom/MetricDOCustomMapper.xml | 25 +++ .../semantic/query/parser/QueryParser.java | 3 +- .../calcite/sql/render/SourceRender.java | 37 ++-- .../parser/convert/CalculateAggConverter.java | 8 +- .../semantic/query/utils/DateUtils.java | 12 +- .../query/utils/QueryStructUtils.java | 4 +- 184 files changed, 2609 insertions(+), 1238 deletions(-) create mode 100644 auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/adaptor/UserAdaptor.java create mode 100644 auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/pojo/Organization.java rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{application/UserServiceImpl.java => adaptor/DefaultUserAdaptor.java} (60%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/interceptor/AuthenticationIgnore.java (79%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/interceptor/AuthenticationInterceptor.java (94%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/interceptor/DefaultAuthenticationInterceptor.java (92%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/interceptor/InterceptorFactory.java (93%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => persistence}/dataobject/UserDO.java (95%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => persistence}/dataobject/UserDOExample.java (99%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{infrastructure => persistence}/mapper/UserDOMapper.java (53%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{infrastructure/repository => persistence/repository/Impl}/UserRepositoryImpl.java (68%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => persistence}/repository/UserRepository.java (50%) create mode 100644 auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/service/UserServiceImpl.java rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/strategy/FakeUserStrategy.java (90%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/strategy/HttpHeaderUserStrategy.java (84%) rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/strategy/UserStrategyFactory.java (93%) create mode 100644 auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/ComponentFactory.java rename auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/{domain => }/utils/UserTokenUtils.java (98%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config/ChatAggConfig.java => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatAggConfigReq.java} (59%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request}/ChatConfigBaseReq.java (66%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request}/ChatConfigEditReqReq.java (67%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request}/ChatConfigFilter.java (83%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultConfig.java => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDefaultConfigReq.java} (59%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailConfig.java => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDetailConfigReq.java} (51%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request}/Entity.java (88%) create mode 100644 chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ExecuteQueryReq.java rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request}/ItemVisibility.java (86%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request}/KnowledgeAdvancedConfig.java (85%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config/KnowledgeInfo.java => api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/KnowledgeInfoReq.java} (88%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/{QueryDataRequest.java => QueryDataReq.java} (95%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/{QueryRequest.java => QueryReq.java} (92%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/{RecommendedQuestion.java => RecommendedQuestionReq.java} (50%) create mode 100644 chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatAggRichConfigResp.java rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/response}/ChatConfigResp.java (56%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigRich.java => api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatConfigRichResp.java} (64%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultRichConfig.java => api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDefaultRichConfigResp.java} (66%) create mode 100644 chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDetailRichConfigResp.java rename chat/{core/src/main/java/com/tencent/supersonic/chat/config/EntityRichInfo.java => api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/EntityRichInfoResp.java} (71%) rename chat/{core/src/main/java/com/tencent/supersonic/chat/config => api/src/main/java/com/tencent/supersonic/chat/api/pojo/response}/ItemVisibilityInfo.java (81%) create mode 100644 chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ParseResp.java rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/{QueryResponse.java => QueryResp.java} (91%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/{RecommendQuestion.java => RecommendQuestionResp.java} (69%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/{RecommendResponse.java => RecommendResp.java} (88%) rename chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/{SearchResponse.java => SearchResp.java} (73%) create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/config/AggregatorConfig.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatAggRichConfig.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailRichConfig.java rename chat/core/src/main/java/com/tencent/supersonic/chat/config/{FunctionCallConfig.java => FunctionCallInfoConfig.java} (87%) create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/mapper/EntityMapper.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionFiled.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/Parameters.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/parser/llm/LLMTimeEnhancementParse.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseConfig.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/ParamOption.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBaseResult.java delete mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityTopNQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricEntityQuery.java create mode 100644 chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatGptHelper.java diff --git a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/adaptor/UserAdaptor.java b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/adaptor/UserAdaptor.java new file mode 100644 index 000000000..33c4cbe6a --- /dev/null +++ b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/adaptor/UserAdaptor.java @@ -0,0 +1,25 @@ +package com.tencent.supersonic.auth.api.authentication.adaptor; + +import com.tencent.supersonic.auth.api.authentication.pojo.Organization; +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.auth.api.authentication.request.UserReq; + +import java.util.List; +import java.util.Set; + +public interface UserAdaptor { + + List getUserNames(); + + List getUserList(); + + List getOrganizationTree(); + + void register(UserReq userReq); + + String login(UserReq userReq); + + List getUserByOrg(String key); + + Set getUserAllOrgId(String userName); +} diff --git a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/pojo/Organization.java b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/pojo/Organization.java new file mode 100644 index 000000000..a72870955 --- /dev/null +++ b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/pojo/Organization.java @@ -0,0 +1,23 @@ +package com.tencent.supersonic.auth.api.authentication.pojo; + +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.List; + +@Data +public class Organization { + + private String id; + + private String parentId; + + private String name; + + private String fullName; + + private List subOrganizations = Lists.newArrayList(); + + private boolean isRoot; + +} diff --git a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/service/UserService.java b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/service/UserService.java index c2c625fc9..dcfb3b8ef 100644 --- a/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/service/UserService.java +++ b/auth/api/src/main/java/com/tencent/supersonic/auth/api/authentication/service/UserService.java @@ -1,9 +1,11 @@ package com.tencent.supersonic.auth.api.authentication.service; +import com.tencent.supersonic.auth.api.authentication.pojo.Organization; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.auth.api.authentication.request.UserReq; import java.util.List; +import java.util.Set; public interface UserService { @@ -14,4 +16,10 @@ public interface UserService { void register(UserReq userCmd); String login(UserReq userCmd); + + Set getUserAllOrgId(String userName); + + List getUserByOrg(String key); + + List getOrganizationTree(); } diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/application/UserServiceImpl.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/adaptor/DefaultUserAdaptor.java similarity index 60% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/application/UserServiceImpl.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/adaptor/DefaultUserAdaptor.java index b8c4d6fb1..e762ca9a3 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/application/UserServiceImpl.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/adaptor/DefaultUserAdaptor.java @@ -1,35 +1,30 @@ -package com.tencent.supersonic.auth.authentication.application; +package com.tencent.supersonic.auth.authentication.adaptor; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor; +import com.tencent.supersonic.auth.api.authentication.pojo.Organization; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword; import com.tencent.supersonic.auth.api.authentication.request.UserReq; -import com.tencent.supersonic.auth.api.authentication.service.UserService; -import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO; -import com.tencent.supersonic.auth.authentication.domain.repository.UserRepository; -import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils; -import java.util.List; -import java.util.stream.Collectors; +import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO; +import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository; +import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils; +import com.tencent.supersonic.common.util.ContextUtils; import org.springframework.beans.BeanUtils; -import org.springframework.stereotype.Service; - -@Service -public class UserServiceImpl implements UserService { - - private UserRepository userRepository; - - private UserTokenUtils userTokenUtils; - - public UserServiceImpl(UserRepository userRepository, UserTokenUtils userTokenUtils) { - this.userRepository = userRepository; - this.userTokenUtils = userTokenUtils; - } +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +public class DefaultUserAdaptor implements UserAdaptor { private List getUserDOList() { + UserRepository userRepository = ContextUtils.getBean(UserRepository.class); return userRepository.getUserList(); } private UserDO getUser(String name) { + UserRepository userRepository = ContextUtils.getBean(UserRepository.class); return userRepository.getUser(name); } @@ -38,22 +33,26 @@ public class UserServiceImpl implements UserService { return getUserDOList().stream().map(UserDO::getName).collect(Collectors.toList()); } - @Override public List getUserList() { List userDOS = getUserDOList(); return userDOS.stream().map(this::convert).collect(Collectors.toList()); } + @Override + public List getOrganizationTree() { + return Lists.newArrayList(); + } + private User convert(UserDO userDO) { User user = new User(); BeanUtils.copyProperties(userDO, user); return user; } - @Override public void register(UserReq userReq) { + UserRepository userRepository = ContextUtils.getBean(UserRepository.class); List userDOS = getUserNames(); if (userDOS.contains(userReq.getName())) { throw new RuntimeException(String.format("user %s exist", userReq.getName())); @@ -65,6 +64,7 @@ public class UserServiceImpl implements UserService { @Override public String login(UserReq userReq) { + UserTokenUtils userTokenUtils = ContextUtils.getBean(UserTokenUtils.class); UserDO userDO = getUser(userReq.getName()); if (userDO == null) { throw new RuntimeException("user not exist,please register"); @@ -77,5 +77,14 @@ public class UserServiceImpl implements UserService { throw new RuntimeException("password not correct, please try again"); } + @Override + public List getUserByOrg(String key) { + return Lists.newArrayList(); + } -} + @Override + public Set getUserAllOrgId(String userName) { + return Sets.newHashSet(); + } + +} \ No newline at end of file diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/AuthenticationIgnore.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/AuthenticationIgnore.java similarity index 79% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/AuthenticationIgnore.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/AuthenticationIgnore.java index 86a45e200..4223c54f6 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/AuthenticationIgnore.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/AuthenticationIgnore.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authentication.domain.interceptor; +package com.tencent.supersonic.auth.authentication.interceptor; import java.lang.annotation.ElementType; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/AuthenticationInterceptor.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/AuthenticationInterceptor.java similarity index 94% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/AuthenticationInterceptor.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/AuthenticationInterceptor.java index 433a0ddd1..0d52c9566 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/AuthenticationInterceptor.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/AuthenticationInterceptor.java @@ -1,9 +1,9 @@ -package com.tencent.supersonic.auth.authentication.domain.interceptor; +package com.tencent.supersonic.auth.authentication.interceptor; import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig; import com.tencent.supersonic.auth.api.authentication.constant.UserConstants; -import com.tencent.supersonic.auth.authentication.application.UserServiceImpl; -import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils; +import com.tencent.supersonic.auth.authentication.service.UserServiceImpl; +import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils; import com.tencent.supersonic.common.util.S2ThreadContext; import java.lang.reflect.Field; import java.util.Arrays; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/DefaultAuthenticationInterceptor.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/DefaultAuthenticationInterceptor.java similarity index 92% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/DefaultAuthenticationInterceptor.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/DefaultAuthenticationInterceptor.java index 23a72cf49..968085d2b 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/DefaultAuthenticationInterceptor.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/DefaultAuthenticationInterceptor.java @@ -1,11 +1,11 @@ -package com.tencent.supersonic.auth.authentication.domain.interceptor; +package com.tencent.supersonic.auth.authentication.interceptor; import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword; -import com.tencent.supersonic.auth.authentication.application.UserServiceImpl; -import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils; +import com.tencent.supersonic.auth.authentication.service.UserServiceImpl; +import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils; import com.tencent.supersonic.common.pojo.exception.AccessException; import com.tencent.supersonic.common.util.ContextUtils; import com.tencent.supersonic.common.util.S2ThreadContext; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/InterceptorFactory.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/InterceptorFactory.java similarity index 93% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/InterceptorFactory.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/InterceptorFactory.java index a67d5caec..b754c3980 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/interceptor/InterceptorFactory.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/interceptor/InterceptorFactory.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authentication.domain.interceptor; +package com.tencent.supersonic.auth.authentication.interceptor; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.SpringFactoriesLoader; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDO.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/dataobject/UserDO.java similarity index 95% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDO.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/dataobject/UserDO.java index 334b4fde6..77b4ae9e7 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDO.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/dataobject/UserDO.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authentication.domain.dataobject; +package com.tencent.supersonic.auth.authentication.persistence.dataobject; public class UserDO { diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDOExample.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/dataobject/UserDOExample.java similarity index 99% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDOExample.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/dataobject/UserDOExample.java index 522407eff..21f01f4ca 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/dataobject/UserDOExample.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/dataobject/UserDOExample.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authentication.domain.dataobject; +package com.tencent.supersonic.auth.authentication.persistence.dataobject; import java.util.ArrayList; import java.util.List; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/infrastructure/mapper/UserDOMapper.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/mapper/UserDOMapper.java similarity index 53% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/infrastructure/mapper/UserDOMapper.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/mapper/UserDOMapper.java index c3b5dc43e..bbda545d2 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/infrastructure/mapper/UserDOMapper.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/mapper/UserDOMapper.java @@ -1,8 +1,8 @@ -package com.tencent.supersonic.auth.authentication.infrastructure.mapper; +package com.tencent.supersonic.auth.authentication.persistence.mapper; -import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO; -import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDOExample; +import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO; +import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDOExample; import java.util.List; import org.apache.ibatis.annotations.Mapper; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/infrastructure/repository/UserRepositoryImpl.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/Impl/UserRepositoryImpl.java similarity index 68% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/infrastructure/repository/UserRepositoryImpl.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/Impl/UserRepositoryImpl.java index 4c9d8a958..97360894b 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/infrastructure/repository/UserRepositoryImpl.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/Impl/UserRepositoryImpl.java @@ -1,10 +1,10 @@ -package com.tencent.supersonic.auth.authentication.infrastructure.repository; +package com.tencent.supersonic.auth.authentication.persistence.repository.Impl; -import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO; -import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDOExample; -import com.tencent.supersonic.auth.authentication.domain.repository.UserRepository; -import com.tencent.supersonic.auth.authentication.infrastructure.mapper.UserDOMapper; +import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO; +import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDOExample; +import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository; +import com.tencent.supersonic.auth.authentication.persistence.mapper.UserDOMapper; import java.util.List; import java.util.Optional; import org.springframework.stereotype.Component; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/repository/UserRepository.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/UserRepository.java similarity index 50% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/repository/UserRepository.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/UserRepository.java index fbc63c595..14037edfb 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/repository/UserRepository.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/UserRepository.java @@ -1,6 +1,6 @@ -package com.tencent.supersonic.auth.authentication.domain.repository; +package com.tencent.supersonic.auth.authentication.persistence.repository; -import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO; +import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO; import java.util.List; public interface UserRepository { diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/rest/UserController.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/rest/UserController.java index 508297110..14cc106d8 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/rest/UserController.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/rest/UserController.java @@ -1,19 +1,17 @@ package com.tencent.supersonic.auth.authentication.rest; +import com.tencent.supersonic.auth.api.authentication.pojo.Organization; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.auth.api.authentication.request.UserReq; import com.tencent.supersonic.auth.api.authentication.service.UserService; import com.tencent.supersonic.auth.api.authentication.utils.UserHolder; import java.util.List; +import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth/user") @@ -32,7 +30,6 @@ public class UserController { return UserHolder.findUser(httpServletRequest, httpServletResponse); } - @GetMapping("/getUserNames") public List getUserNames() { return userService.getUserNames(); @@ -43,6 +40,21 @@ public class UserController { return userService.getUserList(); } + @GetMapping("/getOrganizationTree") + public List getOrganizationTree() { + return userService.getOrganizationTree(); + } + + @GetMapping("/getUserAllOrgId/{userName}") + public Set getUserAllOrgId(@PathVariable("userName") String userName) { + return userService.getUserAllOrgId(userName); + } + + @GetMapping("/getUserByOrg/{org}") + public List getUserByOrg(@PathVariable("org") String org) { + return userService.getUserByOrg(org); + } + @PostMapping("/register") public void register(@RequestBody UserReq userCmd) { userService.register(userCmd); diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/service/UserServiceImpl.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/service/UserServiceImpl.java new file mode 100644 index 000000000..15b102991 --- /dev/null +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/service/UserServiceImpl.java @@ -0,0 +1,52 @@ +package com.tencent.supersonic.auth.authentication.service; + +import com.tencent.supersonic.auth.api.authentication.pojo.Organization; +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.auth.api.authentication.request.UserReq; +import com.tencent.supersonic.auth.api.authentication.service.UserService; +import com.tencent.supersonic.auth.authentication.utils.ComponentFactory; +import java.util.List; +import java.util.Set; + +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + + @Override + public List getUserNames() { + return ComponentFactory.getUserAdaptor().getUserNames(); + } + + @Override + public List getUserList() { + return ComponentFactory.getUserAdaptor().getUserList(); + } + + @Override + public Set getUserAllOrgId(String userName) { + return ComponentFactory.getUserAdaptor().getUserAllOrgId(userName); + } + + @Override + public List getUserByOrg(String key) { + return ComponentFactory.getUserAdaptor().getUserByOrg(key); + } + + @Override + public List getOrganizationTree() { + return ComponentFactory.getUserAdaptor().getOrganizationTree(); + } + + @Override + public void register(UserReq userReq) { + ComponentFactory.getUserAdaptor().register(userReq); + } + + @Override + public String login(UserReq userReq) { + return ComponentFactory.getUserAdaptor().login(userReq); + } + +} diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/FakeUserStrategy.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/FakeUserStrategy.java similarity index 90% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/FakeUserStrategy.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/FakeUserStrategy.java index 9054d8968..a494c158d 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/FakeUserStrategy.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/FakeUserStrategy.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authentication.domain.strategy; +package com.tencent.supersonic.auth.authentication.strategy; import com.tencent.supersonic.auth.api.authentication.pojo.User; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/HttpHeaderUserStrategy.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/HttpHeaderUserStrategy.java similarity index 84% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/HttpHeaderUserStrategy.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/HttpHeaderUserStrategy.java index e1af56208..8da6fe324 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/HttpHeaderUserStrategy.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/HttpHeaderUserStrategy.java @@ -1,8 +1,8 @@ -package com.tencent.supersonic.auth.authentication.domain.strategy; +package com.tencent.supersonic.auth.authentication.strategy; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.auth.api.authentication.service.UserStrategy; -import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils; +import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Service; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/UserStrategyFactory.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/UserStrategyFactory.java similarity index 93% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/UserStrategyFactory.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/UserStrategyFactory.java index cacc32fe0..7732a51b2 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/strategy/UserStrategyFactory.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/strategy/UserStrategyFactory.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authentication.domain.strategy; +package com.tencent.supersonic.auth.authentication.strategy; import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig; diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/ComponentFactory.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/ComponentFactory.java new file mode 100644 index 000000000..e6cee4640 --- /dev/null +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/ComponentFactory.java @@ -0,0 +1,23 @@ +package com.tencent.supersonic.auth.authentication.utils; + +import com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor; +import org.springframework.core.io.support.SpringFactoriesLoader; +import java.util.Objects; + +public class ComponentFactory { + + private static UserAdaptor userAdaptor; + + public static UserAdaptor getUserAdaptor() { + if (Objects.isNull(userAdaptor)) { + userAdaptor = init(UserAdaptor.class); + } + return userAdaptor; + } + + private static T init(Class factoryType) { + return SpringFactoriesLoader.loadFactories(factoryType, + Thread.currentThread().getContextClassLoader()).get(0); + } + +} diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/utils/UserTokenUtils.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/UserTokenUtils.java similarity index 98% rename from auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/utils/UserTokenUtils.java rename to auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/UserTokenUtils.java index 94f76b91c..4c55a84b1 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/domain/utils/UserTokenUtils.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/UserTokenUtils.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.auth.authentication.domain.utils; +package com.tencent.supersonic.auth.authentication.utils; import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_ALGORITHM; import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_CREATE_TIME; diff --git a/auth/authentication/src/main/resources/mapper/UserDOMapper.xml b/auth/authentication/src/main/resources/mapper/UserDOMapper.xml index ebef375fd..15eb2b49c 100644 --- a/auth/authentication/src/main/resources/mapper/UserDOMapper.xml +++ b/auth/authentication/src/main/resources/mapper/UserDOMapper.xml @@ -1,7 +1,7 @@ - - + + @@ -40,7 +40,7 @@ id, name, password, display_name, email - select distinct @@ -67,13 +67,13 @@ delete from s2_user where id = #{id,jdbcType=BIGINT} - + insert into s2_user (id, name, password, display_name, email) values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{displayName,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}) - + insert into s2_user @@ -110,13 +110,13 @@ - select count(*) from s2_user - + update s2_user @@ -134,7 +134,7 @@ where id = #{id,jdbcType=BIGINT} - + update s2_user set name = #{name,jdbcType=VARCHAR}, password = #{password,jdbcType=VARCHAR}, diff --git a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java b/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java index d797b2f1e..878c7cfd4 100644 --- a/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java +++ b/auth/authorization/src/main/java/com/tencent/supersonic/auth/authorization/application/AuthServiceImpl.java @@ -2,6 +2,7 @@ package com.tencent.supersonic.auth.authorization.application; import com.google.common.base.Strings; import com.google.gson.Gson; +import com.tencent.supersonic.auth.api.authentication.service.UserService; import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes; import com.tencent.supersonic.auth.api.authorization.pojo.AuthResGrp; import com.tencent.supersonic.auth.api.authorization.pojo.DimensionFilter; @@ -18,10 +19,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; @Service @@ -30,8 +28,12 @@ public class AuthServiceImpl implements AuthService { private JdbcTemplate jdbcTemplate; - public AuthServiceImpl(JdbcTemplate jdbcTemplate) { + private UserService userService; + + public AuthServiceImpl(JdbcTemplate jdbcTemplate, + UserService userService) { this.jdbcTemplate = jdbcTemplate; + this.userService = userService; } private List load() { @@ -75,6 +77,10 @@ public class AuthServiceImpl implements AuthService { @Override public AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request) { + Set userOrgIds = userService.getUserAllOrgId(req.getUser()); + if (!CollectionUtils.isEmpty(userOrgIds)) { + req.setDepartmentIds(new ArrayList<>(userOrgIds)); + } List groups = getAuthGroups(req); AuthorizedResourceResp resource = new AuthorizedResourceResp(); Map> authGroupsByDomainId = groups.stream() @@ -119,7 +125,6 @@ public class AuthServiceImpl implements AuthService { } } } - return resource; } @@ -133,9 +138,9 @@ public class AuthServiceImpl implements AuthService { .contains(req.getUser())) { return true; } - for (String deparmentId : req.getDepartmentIds()) { + for (String departmentId : req.getDepartmentIds()) { if (!CollectionUtils.isEmpty(group.getAuthorizedDepartmentIds()) - && group.getAuthorizedDepartmentIds().contains(deparmentId)) { + && group.getAuthorizedDepartmentIds().contains(departmentId)) { return true; } } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java index f8ddf147a..def007dca 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/component/SemanticQuery.java @@ -16,4 +16,6 @@ public interface SemanticQuery { QueryResult execute(User user) throws SqlParseException; SemanticParseInfo getParseInfo(); + + void setParseInfo(SemanticParseInfo parseInfo); } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/DomainSchema.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/DomainSchema.java index d1aeb1855..83a1368d3 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/DomainSchema.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/DomainSchema.java @@ -13,11 +13,15 @@ public class DomainSchema { private Set metrics = new HashSet<>(); private Set dimensions = new HashSet<>(); private Set dimensionValues = new HashSet<>(); - private Set entities = new HashSet<>(); + private SchemaElement entity = new SchemaElement(); public SchemaElement getElement(SchemaElementType elementType, long elementID) { Optional element = Optional.empty(); + switch (elementType) { + case ENTITY: + element = Optional.ofNullable(entity); + break; case DOMAIN: element = Optional.of(domain); break; @@ -27,9 +31,6 @@ public class DomainSchema { case DIMENSION: element = dimensions.stream().filter(e -> e.getId() == elementID).findFirst(); break; - case ENTITY: - element = entities.stream().filter(e -> e.getId() == elementID).findFirst(); - break; case VALUE: element = dimensionValues.stream().filter(e -> e.getId() == elementID).findFirst(); default: diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryContext.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryContext.java index ba92177af..244468b9b 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryContext.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/QueryContext.java @@ -1,7 +1,7 @@ package com.tencent.supersonic.chat.api.pojo; import com.tencent.supersonic.chat.api.component.SemanticQuery; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import lombok.Data; import java.util.ArrayList; @@ -10,11 +10,11 @@ import java.util.List; @Data public class QueryContext { - private QueryRequest request; + private QueryReq request; private List candidateQueries = new ArrayList<>(); private SchemaMapInfo mapInfo = new SchemaMapInfo(); - public QueryContext(QueryRequest request) { + public QueryContext(QueryReq request) { this.request = request; } } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElement.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElement.java index 0907c25c9..6390545fd 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElement.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElement.java @@ -5,11 +5,13 @@ import com.google.common.base.Objects; import java.io.Serializable; import java.util.List; -import lombok.Builder; -import lombok.Data; +import lombok.*; @Data +@Getter @Builder +@NoArgsConstructor +//@AllArgsConstructor public class SchemaElement implements Serializable { private Long domain; @@ -20,8 +22,8 @@ public class SchemaElement implements Serializable { private SchemaElementType type; private List alias; - public SchemaElement() { - } +// public SchemaElement() { +// } public SchemaElement(Long domain, Long id, String name, String bizName, Long useCnt, SchemaElementType type, List alias) { diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java index 605023199..e958e24e4 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SchemaElementMatch.java @@ -18,4 +18,10 @@ public class SchemaElementMatch { String detectWord; String word; Long frequency; + MatchMode mode = MatchMode.CURRENT; + + public enum MatchMode { + CURRENT, + INHERIT + } } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java index b0059cfc7..797800d5f 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticParseInfo.java @@ -1,36 +1,32 @@ package com.tencent.supersonic.chat.api.pojo; +import java.util.*; + import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.common.pojo.Order; import com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; import lombok.Data; @Data public class SemanticParseInfo { - String queryMode; - SchemaElement domain; - Set metrics = new LinkedHashSet(); - Set dimensions = new LinkedHashSet(); - Long entity = 0L; - AggregateTypeEnum aggType = AggregateTypeEnum.NONE; - Set dimensionFilters = new LinkedHashSet(); - Set metricFilters = new LinkedHashSet(); + private String queryMode; + private SchemaElement domain; + private Set metrics = new TreeSet<>(new SchemaNameLengthComparator()); + private Set dimensions = new LinkedHashSet(); + private SchemaElement entity; + private AggregateTypeEnum aggType = AggregateTypeEnum.NONE; + private Set dimensionFilters = new LinkedHashSet(); + private Set metricFilters = new LinkedHashSet(); private Set orders = new LinkedHashSet(); private DateConf dateInfo; private Long limit; private Boolean nativeQuery = false; - private Double bonus = 0d; + private double score; private List elementMatches = new ArrayList<>(); - private Map properties; + private Map properties = new HashMap<>(); public Long getDomainId() { return domain != null ? domain.getId() : 0L; @@ -40,8 +36,9 @@ public class SemanticParseInfo { return domain != null ? domain.getName() : "null"; } - public Set getMetrics() { - this.metrics = this.metrics.stream().sorted((o1, o2) -> { + private static class SchemaNameLengthComparator implements Comparator { + @Override + public int compare(SchemaElement o1, SchemaElement o2) { int len1 = o1.getName().length(); int len2 = o2.getName().length(); if (len1 != len2) { @@ -49,7 +46,7 @@ public class SemanticParseInfo { } else { return o1.getName().compareTo(o2.getName()); } - }).collect(Collectors.toCollection(LinkedHashSet::new)); - return this.metrics; + } } + } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticSchema.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticSchema.java index 7e199f00d..6c9e86d44 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticSchema.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/SemanticSchema.java @@ -48,7 +48,7 @@ public class SemanticSchema implements Serializable { public List getEntities() { List entities = new ArrayList<>(); - domainSchemaList.stream().forEach(d -> entities.addAll(d.getEntities())); + domainSchemaList.stream().forEach(d -> entities.add(d.getEntity())); return entities; } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatAggConfig.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatAggConfigReq.java similarity index 59% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatAggConfig.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatAggConfigReq.java index 32f3ba11c..56d83c326 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatAggConfig.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatAggConfigReq.java @@ -1,11 +1,11 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import lombok.Data; import java.util.List; @Data -public class ChatAggConfig { +public class ChatAggConfigReq { /** * invisible dimensions/metrics @@ -15,10 +15,10 @@ public class ChatAggConfig { /** * information about dictionary about the domain */ - private List knowledgeInfos; + private List knowledgeInfos; private KnowledgeAdvancedConfig globalKnowledgeConfig; - private ChatDefaultConfig chatDefaultConfig; + private ChatDefaultConfigReq chatDefaultConfig; } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigBaseReq.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigBaseReq.java similarity index 66% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigBaseReq.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigBaseReq.java index c172bf8b4..b1b64d647 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigBaseReq.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigBaseReq.java @@ -1,6 +1,5 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; -import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestion; import com.tencent.supersonic.common.pojo.enums.StatusEnum; import lombok.Data; @@ -20,18 +19,18 @@ public class ChatConfigBaseReq { /** * the chatDetailConfig about the domain */ - private ChatDetailConfig chatDetailConfig; + private ChatDetailConfigReq chatDetailConfig; /** * the chatAggConfig about the domain */ - private ChatAggConfig chatAggConfig; + private ChatAggConfigReq chatAggConfig; /** * the recommended questions about the domain */ - private List recommendedQuestions; + private List recommendedQuestions; /** * available status diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigEditReqReq.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigEditReqReq.java similarity index 67% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigEditReqReq.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigEditReqReq.java index 441e8f59d..638b03a64 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigEditReqReq.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigEditReqReq.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import lombok.Data; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigFilter.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigFilter.java similarity index 83% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigFilter.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigFilter.java index 721bd4ce0..61cbdfe2c 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigFilter.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatConfigFilter.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import com.tencent.supersonic.common.pojo.enums.StatusEnum; import lombok.Data; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultConfig.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDefaultConfigReq.java similarity index 59% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultConfig.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDefaultConfigReq.java index c69eeade3..33ca4889c 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultConfig.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDefaultConfigReq.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import com.tencent.supersonic.common.pojo.Constants; @@ -8,7 +8,7 @@ import java.util.ArrayList; import java.util.List; @Data -public class ChatDefaultConfig { +public class ChatDefaultConfigReq { private List dimensionIds = new ArrayList<>(); private List metricIds = new ArrayList<>(); @@ -24,4 +24,15 @@ public class ChatDefaultConfig { */ private String period = Constants.DAY; + private TimeMode timeMode = TimeMode.LAST; + + public enum TimeMode { + /** + * date mode + * LAST - a certain time + * RECENT - a period time + */ + LAST, RECENT + } + } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailConfig.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDetailConfigReq.java similarity index 51% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailConfig.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDetailConfigReq.java index 42b6f7d98..6d3468c7f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailConfig.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ChatDetailConfigReq.java @@ -1,11 +1,11 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import lombok.Data; import java.util.List; @Data -public class ChatDetailConfig { +public class ChatDetailConfigReq { /** * invisible dimensions/metrics @@ -15,15 +15,10 @@ public class ChatDetailConfig { /** * information about dictionary about the domain */ - private List knowledgeInfos; + private List knowledgeInfos; private KnowledgeAdvancedConfig globalKnowledgeConfig; - private ChatDefaultConfig chatDefaultConfig; - - /** - * the entity info about the domain - */ - private Entity entity; + private ChatDefaultConfigReq chatDefaultConfig; } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/Entity.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/Entity.java similarity index 88% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/Entity.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/Entity.java index 26a1d415c..373b3cc11 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/Entity.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/Entity.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import java.util.List; import lombok.AllArgsConstructor; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ExecuteQueryReq.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ExecuteQueryReq.java new file mode 100644 index 000000000..b1f4d6013 --- /dev/null +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ExecuteQueryReq.java @@ -0,0 +1,15 @@ +package com.tencent.supersonic.chat.api.pojo.request; + + +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import lombok.Data; + +@Data +public class ExecuteQueryReq { + private User user; + private Integer chatId; + private String queryText; + private SemanticParseInfo parseInfo; + private boolean saveAnswer = true; +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ItemVisibility.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ItemVisibility.java similarity index 86% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ItemVisibility.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ItemVisibility.java index e554e92ed..ee3a1eb13 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ItemVisibility.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/ItemVisibility.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import java.util.ArrayList; import java.util.List; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/KnowledgeAdvancedConfig.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/KnowledgeAdvancedConfig.java similarity index 85% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/KnowledgeAdvancedConfig.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/KnowledgeAdvancedConfig.java index 8ba0dabf1..3c5d8d501 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/KnowledgeAdvancedConfig.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/KnowledgeAdvancedConfig.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import lombok.Data; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/KnowledgeInfo.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/KnowledgeInfoReq.java similarity index 88% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/KnowledgeInfo.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/KnowledgeInfoReq.java index 7a0686ed0..0ec5707db 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/KnowledgeInfo.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/KnowledgeInfoReq.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.request; import com.tencent.supersonic.common.pojo.enums.TypeEnums; @@ -11,7 +11,7 @@ import lombok.Data; */ @Data -public class KnowledgeInfo { +public class KnowledgeInfoReq { /** * metricId、DimensionId、domainId diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/PluginQueryReq.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/PluginQueryReq.java index f3ba04966..14939bc63 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/PluginQueryReq.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/PluginQueryReq.java @@ -7,10 +7,9 @@ import lombok.Data; public class PluginQueryReq { - private String showElementId; + private String name; - //DASHBOARD WIDGET - private String showType; + private String parseMode; private String type; @@ -18,5 +17,5 @@ public class PluginQueryReq { private String pattern; - + private String createdBy; } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryDataRequest.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryDataReq.java similarity index 95% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryDataRequest.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryDataReq.java index 73ef07130..1dbaa4137 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryDataRequest.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryDataReq.java @@ -10,7 +10,7 @@ import com.tencent.supersonic.common.pojo.Order; import lombok.Data; @Data -public class QueryDataRequest { +public class QueryDataReq { String queryMode; SchemaElement domain; Set metrics = new HashSet<>(); diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryRequest.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryReq.java similarity index 92% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryRequest.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryReq.java index d51ccee2f..0e8c686ad 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryRequest.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/QueryReq.java @@ -4,8 +4,7 @@ import com.tencent.supersonic.auth.api.authentication.pojo.User; import lombok.Data; @Data -public class QueryRequest { - +public class QueryReq { private String queryText; private Integer chatId; private Long domainId = 0L; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/RecommendedQuestion.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/RecommendedQuestionReq.java similarity index 50% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/RecommendedQuestion.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/RecommendedQuestionReq.java index 0aa8a6bdf..cbfebb407 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/RecommendedQuestion.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/request/RecommendedQuestionReq.java @@ -1,11 +1,15 @@ package com.tencent.supersonic.chat.api.pojo.request; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.ToString; @Data @ToString -public class RecommendedQuestion { +@AllArgsConstructor +@NoArgsConstructor +public class RecommendedQuestionReq { private String question; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatAggRichConfigResp.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatAggRichConfigResp.java new file mode 100644 index 000000000..b69a16f3c --- /dev/null +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatAggRichConfigResp.java @@ -0,0 +1,26 @@ +package com.tencent.supersonic.chat.api.pojo.response; + +import com.tencent.supersonic.chat.api.pojo.request.KnowledgeAdvancedConfig; +import com.tencent.supersonic.chat.api.pojo.request.KnowledgeInfoReq; +import lombok.Data; + +import java.util.List; + +@Data +public class ChatAggRichConfigResp { + + /** + * invisible dimensions/metrics + */ + private ItemVisibilityInfo visibility; + + /** + * information about dictionary about the domain + */ + private List knowledgeInfos; + + private KnowledgeAdvancedConfig globalKnowledgeConfig; + + private ChatDefaultRichConfigResp chatDefaultConfig; + +} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigResp.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatConfigResp.java similarity index 56% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigResp.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatConfigResp.java index 6c7ecba88..c32efdd1b 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigResp.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatConfigResp.java @@ -1,6 +1,8 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.response; -import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestion; +import com.tencent.supersonic.chat.api.pojo.request.ChatAggConfigReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatDetailConfigReq; +import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestionReq; import com.tencent.supersonic.common.pojo.enums.StatusEnum; import java.util.Date; @@ -15,11 +17,11 @@ public class ChatConfigResp { private Long domainId; - private ChatDetailConfig chatDetailConfig; + private ChatDetailConfigReq chatDetailConfig; - private ChatAggConfig chatAggConfig; + private ChatAggConfigReq chatAggConfig; - private List recommendedQuestions; + private List recommendedQuestions; /** * available status diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigRich.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatConfigRichResp.java similarity index 64% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigRich.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatConfigRichResp.java index f2913012b..e6f8e75a3 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfigRich.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatConfigRichResp.java @@ -1,6 +1,6 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.response; -import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestion; +import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestionReq; import com.tencent.supersonic.common.pojo.enums.StatusEnum; import java.util.Date; import java.util.List; @@ -8,7 +8,7 @@ import java.util.List; import lombok.Data; @Data -public class ChatConfigRich { +public class ChatConfigRichResp { private Long id; @@ -17,11 +17,11 @@ public class ChatConfigRich { private String domainName; private String bizName; - private ChatAggRichConfig chatAggRichConfig; + private ChatAggRichConfigResp chatAggRichConfig; - private ChatDetailRichConfig chatDetailRichConfig; + private ChatDetailRichConfigResp chatDetailRichConfig; - private List recommendedQuestions; + private List recommendedQuestions; /** * available status diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultRichConfig.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDefaultRichConfigResp.java similarity index 66% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultRichConfig.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDefaultRichConfigResp.java index 5b63e9ee7..6ef966681 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDefaultRichConfig.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDefaultRichConfigResp.java @@ -1,14 +1,15 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.response; import com.tencent.supersonic.chat.api.pojo.SchemaElement; +import com.tencent.supersonic.chat.api.pojo.request.ChatDefaultConfigReq; import com.tencent.supersonic.common.pojo.Constants; import lombok.Data; import java.util.List; @Data -public class ChatDefaultRichConfig { +public class ChatDefaultRichConfigResp { private List dimensions; private List metrics; @@ -25,4 +26,6 @@ public class ChatDefaultRichConfig { */ private String period = Constants.DAY; + private ChatDefaultConfigReq.TimeMode timeMode; + } \ No newline at end of file diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDetailRichConfigResp.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDetailRichConfigResp.java new file mode 100644 index 000000000..a3dbd60d8 --- /dev/null +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ChatDetailRichConfigResp.java @@ -0,0 +1,27 @@ +package com.tencent.supersonic.chat.api.pojo.response; + +import com.tencent.supersonic.chat.api.pojo.request.KnowledgeAdvancedConfig; +import com.tencent.supersonic.chat.api.pojo.request.KnowledgeInfoReq; +import lombok.Data; + +import java.util.List; + +@Data +public class ChatDetailRichConfigResp { + + /** + * invisible dimensions/metrics + */ + private ItemVisibilityInfo visibility; + + /** + * information about dictionary about the domain + */ + private List knowledgeInfos; + + private KnowledgeAdvancedConfig globalKnowledgeConfig; + + private ChatDefaultRichConfigResp chatDefaultConfig; + + +} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/EntityRichInfo.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/EntityRichInfoResp.java similarity index 71% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/EntityRichInfo.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/EntityRichInfoResp.java index 023b6e770..a8610fed1 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/EntityRichInfo.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/EntityRichInfoResp.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.response; import com.tencent.supersonic.chat.api.pojo.SchemaElement; @@ -6,7 +6,7 @@ import java.util.List; import lombok.Data; @Data -public class EntityRichInfo { +public class EntityRichInfoResp { /** * entity alias */ diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ItemVisibilityInfo.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ItemVisibilityInfo.java similarity index 81% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/ItemVisibilityInfo.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ItemVisibilityInfo.java index 7a66b15df..3bb4e0798 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ItemVisibilityInfo.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ItemVisibilityInfo.java @@ -1,4 +1,4 @@ -package com.tencent.supersonic.chat.config; +package com.tencent.supersonic.chat.api.pojo.response; import java.util.List; import lombok.Data; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/MetricInfo.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/MetricInfo.java index ef9264145..1c1c4c580 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/MetricInfo.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/MetricInfo.java @@ -7,6 +7,7 @@ import lombok.Data; public class MetricInfo { private String name; + private String dimension; private String value; private String date; private Map statistics; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ParseResp.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ParseResp.java new file mode 100644 index 000000000..6fd7f33e2 --- /dev/null +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/ParseResp.java @@ -0,0 +1,25 @@ +package com.tencent.supersonic.chat.api.pojo.response; + +import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import lombok.*; + +import java.util.List; + +@Data +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ParseResp { + private Integer chatId; + private String queryText; + private ParseState state; + private List selectedParses; + private List candidateParses; + + public enum ParseState { + COMPLETED, + PENDING, + FAILED + } +} diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/QueryResponse.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/QueryResp.java similarity index 91% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/QueryResponse.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/QueryResp.java index 2ab39fb93..1c50eeb05 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/QueryResponse.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/QueryResp.java @@ -4,7 +4,7 @@ import java.util.Date; import lombok.Data; @Data -public class QueryResponse { +public class QueryResp { private Long questionId; private Date createTime; diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendQuestion.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendQuestionResp.java similarity index 69% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendQuestion.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendQuestionResp.java index 67cec7302..62d106faf 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendQuestion.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendQuestionResp.java @@ -1,6 +1,6 @@ package com.tencent.supersonic.chat.api.pojo.response; -import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestion; +import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestionReq; import lombok.AllArgsConstructor; import lombok.Data; @@ -8,7 +8,7 @@ import java.util.List; @Data @AllArgsConstructor -public class RecommendQuestion { +public class RecommendQuestionResp { private Long domainId; - private List recommendedQuestions; + private List recommendedQuestions; } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendResponse.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendResp.java similarity index 88% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendResponse.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendResp.java index 89fd38a34..8490ab549 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendResponse.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/RecommendResp.java @@ -6,7 +6,7 @@ import lombok.Data; import java.util.List; @Data -public class RecommendResponse { +public class RecommendResp { private List dimensions; private List metrics; } diff --git a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/SearchResponse.java b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/SearchResp.java similarity index 73% rename from chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/SearchResponse.java rename to chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/SearchResp.java index 5f6b406ee..7cf443bd7 100644 --- a/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/SearchResponse.java +++ b/chat/api/src/main/java/com/tencent/supersonic/chat/api/pojo/response/SearchResp.java @@ -6,11 +6,11 @@ import lombok.Getter; import lombok.Setter; @Data -public class SearchResponse { +public class SearchResp { private List searchResults; - public SearchResponse(List searchResults) { + public SearchResp(List searchResults) { this.searchResults = searchResults; } } diff --git a/chat/core/pom.xml b/chat/core/pom.xml index f30672455..860369e16 100644 --- a/chat/core/pom.xml +++ b/chat/core/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> chat com.tencent.supersonic @@ -40,6 +40,11 @@ compile + + com.github.plexpt + chatgpt + 4.1.2 + org.junit.jupiter diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/AggregatorConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/config/AggregatorConfig.java new file mode 100644 index 000000000..934b0ae21 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/config/AggregatorConfig.java @@ -0,0 +1,13 @@ +package com.tencent.supersonic.chat.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +@Configuration +@Data +public class AggregatorConfig { + @Value("${metric.aggregator.ratio.enable:true}") + private Boolean enableRatio; +} + + diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatAggRichConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatAggRichConfig.java deleted file mode 100644 index fbe4b6f96..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatAggRichConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.tencent.supersonic.chat.config; - -import lombok.Data; - -import java.util.List; - -@Data -public class ChatAggRichConfig { - - /** - * invisible dimensions/metrics - */ - private ItemVisibilityInfo visibility; - - /** - * information about dictionary about the domain - */ - private List knowledgeInfos; - - private KnowledgeAdvancedConfig globalKnowledgeConfig; - - private ChatDefaultRichConfig chatDefaultConfig; - -} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfig.java index c41c08960..ecffde18b 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfig.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatConfig.java @@ -1,6 +1,8 @@ package com.tencent.supersonic.chat.config; -import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestion; +import com.tencent.supersonic.chat.api.pojo.request.ChatAggConfigReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatDetailConfigReq; +import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestionReq; import com.tencent.supersonic.common.pojo.enums.StatusEnum; import com.tencent.supersonic.common.pojo.RecordInfo; import lombok.Data; @@ -22,14 +24,14 @@ public class ChatConfig { /** * the chatDetailConfig about the domain */ - private ChatDetailConfig chatDetailConfig; + private ChatDetailConfigReq chatDetailConfig; /** * the chatAggConfig about the domain */ - private ChatAggConfig chatAggConfig; + private ChatAggConfigReq chatAggConfig; - private List recommendedQuestions; + private List recommendedQuestions; /** * available status diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailRichConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailRichConfig.java deleted file mode 100644 index e7f372baf..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/ChatDetailRichConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.tencent.supersonic.chat.config; - -import lombok.Data; - -import java.util.List; - -@Data -public class ChatDetailRichConfig { - - /** - * invisible dimensions/metrics - */ - private ItemVisibilityInfo visibility; - - /** - * the entity info about the domain - */ - private EntityRichInfo entity; - - /** - * information about dictionary about the domain - */ - private List knowledgeInfos; - - private KnowledgeAdvancedConfig globalKnowledgeConfig; - - private ChatDefaultRichConfig chatDefaultConfig; - - -} \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/config/FunctionCallConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/config/FunctionCallInfoConfig.java similarity index 87% rename from chat/core/src/main/java/com/tencent/supersonic/chat/config/FunctionCallConfig.java rename to chat/core/src/main/java/com/tencent/supersonic/chat/config/FunctionCallInfoConfig.java index 1fd4a4083..ec23773bd 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/config/FunctionCallConfig.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/config/FunctionCallInfoConfig.java @@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration; @Configuration @Data -public class FunctionCallConfig { +public class FunctionCallInfoConfig { @Value("${functionCall.url:}") private String url; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/EntityMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/EntityMapper.java new file mode 100644 index 000000000..6ae9ceeba --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/EntityMapper.java @@ -0,0 +1,69 @@ +package com.tencent.supersonic.chat.mapper; + + +import com.tencent.supersonic.chat.api.component.SchemaMapper; +import com.tencent.supersonic.chat.api.pojo.*; +import com.tencent.supersonic.chat.service.SemanticService; +import com.tencent.supersonic.common.util.ContextUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.util.CollectionUtils; +import java.util.List; +import java.util.stream.Collectors; + + +@Slf4j +public class EntityMapper implements SchemaMapper { + + @Override + public void map(QueryContext queryContext) { + SchemaMapInfo schemaMapInfo = queryContext.getMapInfo(); + for (Long domainId : schemaMapInfo.getMatchedDomains()) { + List schemaElementMatchList = schemaMapInfo.getMatchedElements(domainId); + if (CollectionUtils.isEmpty(schemaElementMatchList)) { + continue; + } + SchemaElement entity = getEntity(domainId); + if (entity == null || entity.getId() == null) { + continue; + } + List valueSchemaElements = schemaElementMatchList.stream().filter(schemaElementMatch -> + SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType())) + .collect(Collectors.toList()); + for (SchemaElementMatch schemaElementMatch : valueSchemaElements) { + if (!entity.getId().equals(schemaElementMatch.getElement().getId())){ + continue; + } + if (!checkExistSameEntitySchemaElements(schemaElementMatch, schemaElementMatchList)) { + SchemaElementMatch entitySchemaElementMath = new SchemaElementMatch(); + BeanUtils.copyProperties(schemaElementMatch, entitySchemaElementMath); + entitySchemaElementMath.setElement(entity); + schemaElementMatchList.add(entitySchemaElementMath); + } + schemaElementMatch.getElement().setType(SchemaElementType.ID); + } + } + } + + private boolean checkExistSameEntitySchemaElements(SchemaElementMatch valueSchemaElementMatch, + List schemaElementMatchList) { + List entitySchemaElements = schemaElementMatchList.stream().filter(schemaElementMatch -> + SchemaElementType.ENTITY.equals(schemaElementMatch.getElement().getType())) + .collect(Collectors.toList()); + for (SchemaElementMatch schemaElementMatch : entitySchemaElements) { + if (schemaElementMatch.getElement().getId().equals(valueSchemaElementMatch.getElement().getId())) { + return true; + } + } + return false; + } + + private SchemaElement getEntity(Long domainId) { + SemanticService semanticService = ContextUtils.getBean(SemanticService.class); + DomainSchema domainSchema = semanticService.getDomainSchema(domainId); + if (domainSchema != null && domainSchema.getEntity() != null) { + return domainSchema.getEntity(); + } + return null; + } +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/FuzzyNameMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/FuzzyNameMapper.java index cf3a6129e..470f43236 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/FuzzyNameMapper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/FuzzyNameMapper.java @@ -2,13 +2,9 @@ package com.tencent.supersonic.chat.mapper; import com.hankcs.hanlp.seg.common.Term; import com.tencent.supersonic.chat.api.component.SchemaMapper; +import com.tencent.supersonic.chat.api.pojo.*; import com.tencent.supersonic.chat.api.pojo.QueryContext; -import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; -import com.tencent.supersonic.chat.api.pojo.SchemaElementType; -import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; import com.tencent.supersonic.knowledge.service.SchemaService; -import com.tencent.supersonic.chat.api.pojo.SemanticSchema; -import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.common.util.ContextUtils; import com.tencent.supersonic.knowledge.utils.HanlpHelper; import java.util.ArrayList; @@ -44,7 +40,7 @@ public class FuzzyNameMapper implements SchemaMapper { } private void detectAndAddToSchema(QueryContext queryContext, List terms, List domains, - SchemaElementType schemaElementType) { + SchemaElementType schemaElementType) { try { Map> domainResultSet = getResultSet(queryContext, terms, domains); @@ -57,7 +53,7 @@ public class FuzzyNameMapper implements SchemaMapper { } private Map> getResultSet(QueryContext queryContext, List terms, - List domains) { + List domains) { String queryText = queryContext.getRequest().getQueryText(); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/HanlpDictMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/HanlpDictMapper.java index fb9c94d36..c58e177c4 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/HanlpDictMapper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/HanlpDictMapper.java @@ -74,6 +74,10 @@ public class HanlpDictMapper implements SchemaMapper { Long frequency = wordNatureToFrequency.get(mapResult.getName() + nature); SchemaElement element = domainSchema.getElement(elementType, elementID); + if(Objects.isNull(element)){ + log.info("element is null, elementType:{},elementID:{}", elementType, elementID); + continue; + } if (element.getType().equals(SchemaElementType.VALUE)) { element.setName(mapResult.getName()); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/QueryFilterMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/QueryFilterMapper.java index 74f785f30..9134d1541 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/QueryFilterMapper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/mapper/QueryFilterMapper.java @@ -1,27 +1,41 @@ package com.tencent.supersonic.chat.mapper; +import com.google.common.collect.Lists; import com.tencent.supersonic.chat.api.component.SchemaMapper; import com.tencent.supersonic.chat.api.pojo.*; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; +import com.tencent.supersonic.chat.api.pojo.request.QueryFilters; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Slf4j public class QueryFilterMapper implements SchemaMapper { + private Long FREQUENCY = 9999999L; + private double SIMILARITY = 1.0; + @Override public void map(QueryContext queryContext) { - QueryRequest queryReq = queryContext.getRequest(); + QueryReq queryReq = queryContext.getRequest(); Long domainId = queryReq.getDomainId(); if (domainId == null || domainId <= 0) { return; } SchemaMapInfo schemaMapInfo = queryContext.getMapInfo(); clearOtherSchemaElementMatch(domainId, schemaMapInfo); + List schemaElementMatches = schemaMapInfo.getMatchedElements(domainId); + if (schemaElementMatches == null) { + schemaElementMatches = Lists.newArrayList(); + schemaMapInfo.setMatchedElements(domainId, schemaElementMatches); + } + addValueSchemaElementMatch(schemaElementMatches, queryReq.getQueryFilters()); } - private void clearOtherSchemaElementMatch(Long domainId, SchemaMapInfo schemaMapInfo) { + private void clearOtherSchemaElementMatch(Long domainId, SchemaMapInfo schemaMapInfo) { for (Map.Entry> entry : schemaMapInfo.getDomainElementMatches().entrySet()) { if (!entry.getKey().equals(domainId)) { entry.getValue().clear(); @@ -29,4 +43,44 @@ public class QueryFilterMapper implements SchemaMapper { } } + private List addValueSchemaElementMatch(List candidateElementMatches, + QueryFilters queryFilter) { + if (queryFilter == null || CollectionUtils.isEmpty(queryFilter.getFilters())) { + return candidateElementMatches; + } + for (QueryFilter filter : queryFilter.getFilters()) { + if (checkExistSameValueSchemaElementMatch(filter, candidateElementMatches)) { + continue; + } + SchemaElement element = SchemaElement.builder() + .id(filter.getElementID()) + .name(String.valueOf(filter.getValue())) + .type(SchemaElementType.VALUE) + .bizName(filter.getBizName()) + .build(); + SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder() + .element(element) + .frequency(FREQUENCY) + .word(String.valueOf(filter.getValue())) + .similarity(SIMILARITY) + .detectWord(filter.getName()) + .build(); + candidateElementMatches.add(schemaElementMatch); + } + return candidateElementMatches; + } + + private boolean checkExistSameValueSchemaElementMatch(QueryFilter queryFilter, + List schemaElementMatches) { + List valueSchemaElements = schemaElementMatches.stream().filter(schemaElementMatch -> + SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType())) + .collect(Collectors.toList()); + for (SchemaElementMatch schemaElementMatch : valueSchemaElements) { + if (schemaElementMatch.getElement().getId().equals(queryFilter.getElementID()) + && schemaElementMatch.getWord().equals(String.valueOf(queryFilter.getValue()))) { + return true; + } + } + return false; + } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/SatisfactionChecker.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/SatisfactionChecker.java index 495a73597..b5fb123dd 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/SatisfactionChecker.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/SatisfactionChecker.java @@ -3,13 +3,16 @@ package com.tencent.supersonic.chat.parser; import com.tencent.supersonic.chat.api.component.SemanticQuery; import com.tencent.supersonic.chat.api.pojo.*; +import com.tencent.supersonic.chat.plugin.PluginParseResult; +import com.tencent.supersonic.chat.query.plugin.PluginSemanticQuery; +import com.tencent.supersonic.chat.query.rule.RuleSemanticQuery; import com.tencent.supersonic.common.pojo.Constants; import com.tencent.supersonic.common.pojo.DateConf; +import com.tencent.supersonic.common.util.JsonUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.compress.utils.Lists; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; - import java.util.HashSet; import java.util.List; import java.util.Map; @@ -25,31 +28,36 @@ import java.util.stream.Collectors; public class SatisfactionChecker { private static final double LONG_TEXT_THRESHOLD = 0.8; - private static final double SHORT_TEXT_THRESHOLD = 0.6; + private static final double SHORT_TEXT_THRESHOLD = 0.5; private static final int QUERY_TEXT_LENGTH_THRESHOLD = 10; - - public static final double BONUS_THRESHOLD = 100; + public static final double EMBEDDING_THRESHOLD = 0.2; // check all the parse info in candidate public static boolean check(QueryContext queryCtx) { for (SemanticQuery query : queryCtx.getCandidateQueries()) { - SemanticParseInfo semanticParseInfo = query.getParseInfo(); - Long domainId = semanticParseInfo.getDomainId(); - List schemaElementMatches = queryCtx.getMapInfo() - .getMatchedElements(domainId); - if (check(queryCtx.getRequest().getQueryText(), semanticParseInfo, schemaElementMatches)) { - return true; + if (query instanceof RuleSemanticQuery) { + if (checkRuleThreshHold(queryCtx.getRequest().getQueryText(), query.getParseInfo())) { + return true; + } + } else if (query instanceof PluginSemanticQuery) { + if (checkEmbeddingThreshold(query.getParseInfo())) { + log.info("query mode :{} satisfy check", query.getQueryMode()); + return true; + } } } return false; } + private static boolean checkEmbeddingThreshold(SemanticParseInfo semanticParseInfo) { + Object object = semanticParseInfo.getProperties().get(Constants.CONTEXT); + PluginParseResult pluginParseResult = JsonUtil.toObject(JsonUtil.toString(object), PluginParseResult.class); + return EMBEDDING_THRESHOLD > pluginParseResult.getDistance(); + } + //check single parse info - private static boolean check(String text, SemanticParseInfo semanticParseInfo, - List schemaElementMatches) { - if (semanticParseInfo.getBonus() != null && semanticParseInfo.getBonus() >= BONUS_THRESHOLD) { - return true; - } + private static boolean checkRuleThreshHold(String text, SemanticParseInfo semanticParseInfo) { + List schemaElementMatches = semanticParseInfo.getElementMatches(); if (CollectionUtils.isEmpty(schemaElementMatches)) { return false; } @@ -71,6 +79,11 @@ public class SatisfactionChecker { detectWords.add(schemaElementMatch.getDetectWord()); } } + for (SchemaElementMatch schemaElementMatch : schemaElementMatches) { + if (SchemaElementType.ID.equals(schemaElementMatch.getElement().getType())) { + detectWords.add(schemaElementMatch.getDetectWord()); + } + } for (SchemaElement schemaItem : semanticParseInfo.getMetrics()) { detectWords.add( detectWordMap.getOrDefault(Optional.ofNullable(schemaItem.getId()).orElse(0L), "")); @@ -87,7 +100,7 @@ public class SatisfactionChecker { if (StringUtils.isNotBlank(dateText) && !dateText.equalsIgnoreCase(Constants.NULL)) { detectWords.add(dateText); } - detectWords.removeIf(word -> !text.contains(word)); + detectWords.removeIf(word -> !text.contains(word) && !text.contains(StringUtils.reverse(word))); //compare the length between detect words and query text return checkThreshold(text, detectWords, semanticParseInfo); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingBasedParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingBasedParser.java index 6bbdf299f..e054bd9df 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingBasedParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingBasedParser.java @@ -5,19 +5,21 @@ import com.google.common.collect.Sets; import com.tencent.supersonic.chat.api.component.SemanticParser; import com.tencent.supersonic.chat.api.pojo.*; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; -import com.tencent.supersonic.chat.config.ChatConfigRich; -import com.tencent.supersonic.chat.config.EntityRichInfo; +import com.tencent.supersonic.chat.api.pojo.request.QueryFilters; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.parser.SatisfactionChecker; import com.tencent.supersonic.chat.plugin.Plugin; import com.tencent.supersonic.chat.plugin.PluginManager; +import com.tencent.supersonic.chat.plugin.PluginParseResult; import com.tencent.supersonic.chat.query.QueryManager; import com.tencent.supersonic.chat.query.plugin.PluginSemanticQuery; -import com.tencent.supersonic.chat.service.ConfigService; import com.tencent.supersonic.chat.service.PluginService; +import com.tencent.supersonic.chat.service.SemanticService; import com.tencent.supersonic.common.pojo.Constants; import com.tencent.supersonic.common.util.ContextUtils; import java.util.*; import java.util.stream.Collectors; +import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; @@ -30,30 +32,43 @@ public class EmbeddingBasedParser implements SemanticParser { @Override public void parse(QueryContext queryContext, ChatContext chatContext) { EmbeddingConfig embeddingConfig = ContextUtils.getBean(EmbeddingConfig.class); - if (SatisfactionChecker.check(queryContext) || StringUtils.isBlank(embeddingConfig.getUrl())) { + if (StringUtils.isBlank(embeddingConfig.getUrl())) { return; } log.info("EmbeddingBasedParser parser query ctx: {}, chat ctx: {}", queryContext, chatContext); - for (Long domainId : getDomainMatched(queryContext)) { - String text = replaceText(queryContext, domainId); - List embeddingRetrievals = recallResult(text, hasCandidateQuery(queryContext)); - Optional pluginOptional = choosePlugin(embeddingRetrievals, domainId); - if (pluginOptional.isPresent()) { - Map embeddingRetrievalMap = embeddingRetrievals.stream() - .collect(Collectors.toMap(RecallRetrieval::getId, e -> e, (value1, value2) -> value1)); - Plugin plugin = pluginOptional.get(); - log.info("EmbeddingBasedParser text: {} domain: {} choose plugin: [{} {}]", - text, domainId, plugin.getId(), plugin.getName()); - PluginSemanticQuery pluginQuery = QueryManager.createPluginQuery(plugin.getType()); - SemanticParseInfo semanticParseInfo = buildSemanticParseInfo(queryContext, domainId, - plugin, embeddingRetrievalMap); - semanticParseInfo.setQueryMode(pluginQuery.getQueryMode()); - pluginQuery.setParseInfo(semanticParseInfo); - queryContext.getCandidateQueries().add(pluginQuery); + Set domainIds = getDomainMatched(queryContext); + String text = queryContext.getRequest().getQueryText(); + if (!CollectionUtils.isEmpty(domainIds)) { + for (Long domainId : domainIds) { + List schemaElementMatches = getMatchedElements(queryContext, domainId); + String textReplaced = replaceText(text, schemaElementMatches); + List embeddingRetrievals = recallResult(textReplaced, hasCandidateQuery(queryContext)); + Optional pluginOptional = choosePlugin(embeddingRetrievals, domainId); + log.info("domain id :{} embedding result, text:{} embeddingResp:{} ",domainId, textReplaced, embeddingRetrievals); + pluginOptional.ifPresent(plugin -> buildQuery(plugin, embeddingRetrievals, domainId, textReplaced, queryContext, schemaElementMatches)); } + } else { + List embeddingRetrievals = recallResult(text, hasCandidateQuery(queryContext)); + Optional pluginOptional = choosePlugin(embeddingRetrievals, null); + pluginOptional.ifPresent(plugin -> buildQuery(plugin, embeddingRetrievals, null, text, queryContext, Lists.newArrayList())); } } + private void buildQuery(Plugin plugin, List embeddingRetrievals, + Long domainId, String text, + QueryContext queryContext, List schemaElementMatches) { + Map embeddingRetrievalMap = embeddingRetrievals.stream() + .collect(Collectors.toMap(RecallRetrieval::getId, e -> e, (value1, value2) -> value1)); + log.info("EmbeddingBasedParser text: {} domain: {} choose plugin: [{} {}]", + text, domainId, plugin.getId(), plugin.getName()); + PluginSemanticQuery pluginQuery = QueryManager.createPluginQuery(plugin.getType()); + SemanticParseInfo semanticParseInfo = buildSemanticParseInfo(domainId, plugin, text, + queryContext.getRequest(), embeddingRetrievalMap, schemaElementMatches); + semanticParseInfo.setQueryMode(pluginQuery.getQueryMode()); + pluginQuery.setParseInfo(semanticParseInfo); + queryContext.getCandidateQueries().add(pluginQuery); + } + private Set getDomainMatched(QueryContext queryContext) { Long queryDomainId = queryContext.getRequest().getDomainId(); if (queryDomainId != null && queryDomainId > 0) { @@ -62,51 +77,61 @@ public class EmbeddingBasedParser implements SemanticParser { return queryContext.getMapInfo().getMatchedDomains(); } - private SemanticParseInfo buildSemanticParseInfo(QueryContext queryContext, Long domainId, Plugin plugin, - Map embeddingRetrievalMap) { + private SemanticParseInfo buildSemanticParseInfo(Long domainId, Plugin plugin, String text, QueryReq queryReq, + Map embeddingRetrievalMap, + List schemaElementMatches) { SchemaElement schemaElement = new SchemaElement(); schemaElement.setDomain(domainId); schemaElement.setId(domainId); SemanticParseInfo semanticParseInfo = new SemanticParseInfo(); + semanticParseInfo.setElementMatches(schemaElementMatches); semanticParseInfo.setDomain(schemaElement); - SchemaMapInfo schemaMapInfo = queryContext.getMapInfo(); - if (Double.parseDouble(embeddingRetrievalMap.get(plugin.getId().toString()).getDistance()) < THRESHOLD) { - semanticParseInfo.setBonus(SatisfactionChecker.BONUS_THRESHOLD); - } + double distance = Double.parseDouble(embeddingRetrievalMap.get(plugin.getId().toString()).getDistance()); + double score = text.length() * (1 - distance); Map properties = new HashMap<>(); - properties.put(Constants.CONTEXT, plugin); + PluginParseResult pluginParseResult = new PluginParseResult(); + pluginParseResult.setPlugin(plugin); + pluginParseResult.setRequest(queryReq); + pluginParseResult.setDistance(distance); + properties.put(Constants.CONTEXT, pluginParseResult); semanticParseInfo.setProperties(properties); - semanticParseInfo.setElementMatches(schemaMapInfo.getMatchedElements(domainId)); - fillSemanticParseInfo(queryContext, semanticParseInfo); - setEntityId(domainId, semanticParseInfo); + semanticParseInfo.setScore(score); + fillSemanticParseInfo(semanticParseInfo); + setEntity(domainId, semanticParseInfo); return semanticParseInfo; } - private Optional getEntityElementId(Long domainId) { - ConfigService configService = ContextUtils.getBean(ConfigService.class); - ChatConfigRich chatConfigRich = configService.getConfigRichInfo(domainId); - EntityRichInfo entityRichInfo = chatConfigRich.getChatDetailRichConfig().getEntity(); - if (entityRichInfo != null) { - SchemaElement schemaElement = entityRichInfo.getDimItem(); - if (schemaElement != null) { - return Optional.of(schemaElement.getId()); - } + private List getMatchedElements(QueryContext queryContext, Long domainId) { + SchemaMapInfo schemaMapInfo = queryContext.getMapInfo(); + List schemaElementMatches = schemaMapInfo.getMatchedElements(domainId); + if (schemaElementMatches == null) { + return Lists.newArrayList(); } - return Optional.empty(); + QueryReq queryReq = queryContext.getRequest(); + QueryFilters queryFilters = queryReq.getQueryFilters(); + if (queryFilters == null || CollectionUtils.isEmpty(queryFilters.getFilters())) { + return schemaElementMatches; + } + Map element = queryFilters.getFilters().stream() + .collect(Collectors.toMap(QueryFilter::getElementID, QueryFilter::getValue, (v1, v2) -> v1)); + return schemaElementMatches.stream().filter(schemaElementMatch -> + SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType()) + || SchemaElementType.ID.equals(schemaElementMatch.getElement().getType()) + || SchemaElementType.ENTITY.equals(schemaElementMatch.getElement().getType())) + .filter(schemaElementMatch -> + !element.containsKey(schemaElementMatch.getElement().getId()) || ( + element.containsKey(schemaElementMatch.getElement().getId()) && + element.get(schemaElementMatch.getElement().getId()).toString() + .equalsIgnoreCase(schemaElementMatch.getWord()) + )) + .collect(Collectors.toList()); } - private void setEntityId(Long domainId, SemanticParseInfo semanticParseInfo) { - Optional entityElementIdOptional = getEntityElementId(domainId); - if (entityElementIdOptional.isPresent()) { - Long entityElementId = entityElementIdOptional.get(); - for (QueryFilter filter : semanticParseInfo.getDimensionFilters()) { - if (entityElementId.equals(filter.getElementID())) { - String value = String.valueOf(filter.getValue()); - if (StringUtils.isNumeric(value)) { - semanticParseInfo.setEntity(Long.parseLong(value)); - } - } - } + private void setEntity(Long domainId, SemanticParseInfo semanticParseInfo) { + SemanticService semanticService = ContextUtils.getBean(SemanticService.class); + DomainSchema domainSchema = semanticService.getDomainSchema(domainId); + if (domainSchema != null && domainSchema.getEntity() != null) { + semanticParseInfo.setEntity(domainSchema.getEntity()); } } @@ -120,6 +145,12 @@ public class EmbeddingBasedParser implements SemanticParser { Map pluginMap = plugins.stream().collect(Collectors.toMap(Plugin::getId, p -> p)); for (RecallRetrieval embeddingRetrieval : embeddingRetrievals) { Plugin plugin = pluginMap.get(Long.parseLong(embeddingRetrieval.getId())); + if (plugin == null) { + continue; + } + if (domainId == null) { + return Optional.of(plugin); + } if (!CollectionUtils.isEmpty(plugin.getDomainList()) && plugin.getDomainList().contains(domainId)) { return Optional.of(plugin); } @@ -131,7 +162,6 @@ public class EmbeddingBasedParser implements SemanticParser { try { PluginManager pluginManager = ContextUtils.getBean(PluginManager.class); EmbeddingResp embeddingResp = pluginManager.recognize(embeddingText); - log.info("embedding result, text:{} embeddingResp:{}", embeddingText, embeddingResp); List embeddingRetrievals = embeddingResp.getRetrieval(); if(!CollectionUtils.isEmpty(embeddingRetrievals)){ if (hasCandidateQuery) { @@ -154,25 +184,38 @@ public class EmbeddingBasedParser implements SemanticParser { return !CollectionUtils.isEmpty(queryContext.getCandidateQueries()); } - private void fillSemanticParseInfo(QueryContext queryContext, SemanticParseInfo semanticParseInfo) { - if (queryContext.getRequest().getQueryFilters() != null) { - semanticParseInfo.getDimensionFilters() - .addAll(queryContext.getRequest().getQueryFilters().getFilters()); + private void fillSemanticParseInfo(SemanticParseInfo semanticParseInfo) { + List schemaElementMatches = semanticParseInfo.getElementMatches(); + if (!CollectionUtils.isEmpty(schemaElementMatches)) { + schemaElementMatches.stream().filter(schemaElementMatch -> + SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType()) + || SchemaElementType.ID.equals(schemaElementMatch.getElement().getType())) + .forEach(schemaElementMatch -> { + QueryFilter queryFilter = new QueryFilter(); + queryFilter.setValue(schemaElementMatch.getWord()); + queryFilter.setElementID(schemaElementMatch.getElement().getId()); + queryFilter.setName(schemaElementMatch.getElement().getName()); + queryFilter.setOperator(FilterOperatorEnum.EQUALS); + queryFilter.setBizName(schemaElementMatch.getElement().getBizName()); + semanticParseInfo.getDimensionFilters().add(queryFilter); + }); } } - protected String replaceText(QueryContext queryContext, Long domainId) { - String text = queryContext.getRequest().getQueryText(); - List schemaElementMatches = queryContext.getMapInfo().getMatchedElements(domainId); + protected String replaceText(String text, List schemaElementMatches) { if (CollectionUtils.isEmpty(schemaElementMatches)) { return text; } List valueSchemaElementMatches = schemaElementMatches.stream() .filter(schemaElementMatch -> - SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType())) + SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType()) + || SchemaElementType.ID.equals(schemaElementMatch.getElement().getType())) .collect(Collectors.toList()); for (SchemaElementMatch schemaElementMatch : valueSchemaElementMatches) { String detectWord = schemaElementMatch.getDetectWord(); + if (StringUtils.isBlank(detectWord)) { + continue; + } text = text.replace(detectWord, ""); } return text; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingEntityResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingEntityResolver.java index 6ac6509d5..6a52efead 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingEntityResolver.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/embedding/EmbeddingEntityResolver.java @@ -4,14 +4,10 @@ import com.alibaba.fastjson.JSONObject; import com.tencent.supersonic.chat.api.pojo.*; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.chat.api.pojo.request.QueryFilters; -import com.tencent.supersonic.chat.config.ChatConfigRich; -import com.tencent.supersonic.chat.parser.function.DomainResolver; import com.tencent.supersonic.chat.service.ConfigService; -import com.tencent.supersonic.chat.utils.ComponentFactory; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Component; import java.util.List; @@ -28,18 +24,6 @@ public class EmbeddingEntityResolver { this.configService = configService; } - public Pair getDomainEntityId(QueryContext queryCtx, ChatContext chatCtx) { - DomainResolver domainResolver = ComponentFactory.getDomainResolver(); - Long domainId = domainResolver.resolve(queryCtx, chatCtx); - ChatConfigRich chatConfigRichResp = configService.getConfigRichInfo(domainId); - SchemaElement schemaElement = chatConfigRichResp.getChatDetailRichConfig().getEntity().getDimItem(); - if (schemaElement == null) { - return Pair.of(domainId, null); - } - Long entityId = getEntityValue(domainId, schemaElement.getId(), queryCtx, chatCtx); - return Pair.of(domainId, entityId); - } - private Long getEntityValue(Long domainId, Long entityElementId, QueryContext queryCtx, ChatContext chatCtx) { Long entityId = null; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/DomainResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/DomainResolver.java index 4ecd3e9db..45fc864c8 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/DomainResolver.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/DomainResolver.java @@ -3,9 +3,10 @@ package com.tencent.supersonic.chat.parser.function; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.QueryContext; +import java.util.List; public interface DomainResolver { - Long resolve(QueryContext queryContext, ChatContext chatCtx); + Long resolve(QueryContext queryContext, ChatContext chatCtx, List restrictiveDomains); } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionBasedParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionBasedParser.java index e32ccc9fd..beb862d25 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionBasedParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionBasedParser.java @@ -6,11 +6,11 @@ import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.QueryContext; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.config.FunctionCallConfig; -import com.tencent.supersonic.chat.parser.ParseMode; +import com.tencent.supersonic.chat.config.FunctionCallInfoConfig; import com.tencent.supersonic.chat.parser.SatisfactionChecker; import com.tencent.supersonic.chat.plugin.Plugin; import com.tencent.supersonic.chat.plugin.PluginManager; +import com.tencent.supersonic.chat.plugin.PluginParseConfig; import com.tencent.supersonic.chat.plugin.PluginParseResult; import com.tencent.supersonic.chat.query.QueryManager; import com.tencent.supersonic.chat.query.plugin.PluginSemanticQuery; @@ -22,12 +22,15 @@ import com.tencent.supersonic.common.util.ContextUtils; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; + +import com.tencent.supersonic.common.util.JsonUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpEntity; @@ -44,9 +47,12 @@ public class FunctionBasedParser implements SemanticParser { public static final double FUNCTION_BONUS_THRESHOLD = 200; + public static final double SKIP_DSL_LENGTH = 10; + + @Override public void parse(QueryContext queryCtx, ChatContext chatCtx) { - FunctionCallConfig functionCallConfig = ContextUtils.getBean(FunctionCallConfig.class); + FunctionCallInfoConfig functionCallConfig = ContextUtils.getBean(FunctionCallInfoConfig.class); PluginService pluginService = ContextUtils.getBean(PluginService.class); String functionUrl = functionCallConfig.getUrl(); @@ -55,39 +61,57 @@ public class FunctionBasedParser implements SemanticParser { queryCtx.getRequest().getQueryText()); return; } - DomainResolver domainResolver = ComponentFactory.getDomainResolver(); - Long domainId = domainResolver.resolve(queryCtx, chatCtx); - List functionNames = getFunctionNames(domainId); - log.info("domainId:{},functionNames:{}", domainId, functionNames); - if (Objects.isNull(domainId) || domainId <= 0) { + + Set matchedDomains = getMatchDomains(queryCtx); + List functionNames = getFunctionNames(matchedDomains); + log.info("matchedDomains:{},functionNames:{}", matchedDomains, functionNames); + + if (CollectionUtils.isEmpty(functionNames) || CollectionUtils.isEmpty(matchedDomains)) { return; } + List functionDOList = getFunctionDO(queryCtx.getRequest().getDomainId()); FunctionReq functionReq = FunctionReq.builder() .queryText(queryCtx.getRequest().getQueryText()) - .functionNames(functionNames).build(); - + .pluginConfigs(functionDOList).build(); FunctionResp functionResp = requestFunction(functionUrl, functionReq); log.info("requestFunction result:{}", functionResp.getToolSelection()); - if (Objects.isNull(functionResp) || StringUtils.isBlank(functionResp.getToolSelection())) { + if (skipFunction(queryCtx, functionResp)) { return; } PluginParseResult functionCallParseResult = new PluginParseResult(); String toolSelection = functionResp.getToolSelection(); Optional pluginOptional = pluginService.getPluginByName(toolSelection); - if (pluginOptional.isPresent()) { - toolSelection = pluginOptional.get().getType(); - functionCallParseResult.setPlugin(pluginOptional.get()); + if (!pluginOptional.isPresent()) { + log.info("pluginOptional is not exist:{}, skip the parse", toolSelection); + return; } + Plugin plugin = pluginOptional.get(); + toolSelection = plugin.getType(); + functionCallParseResult.setPlugin(plugin); + log.info("QueryManager PluginQueryModes:{}", QueryManager.getPluginQueryModes()); PluginSemanticQuery semanticQuery = QueryManager.createPluginQuery(toolSelection); + DomainResolver domainResolver = ComponentFactory.getDomainResolver(); + Long domainId = domainResolver.resolve(queryCtx, chatCtx, plugin.getDomainList()); + log.info("FunctionBasedParser domainId:{}",domainId); + if ((Objects.isNull(domainId) || domainId <= 0) && !plugin.isContainsAllDomain()) { + log.info("domain is null, skip the parse, select tool: {}", toolSelection); + return; + } + if (!plugin.getDomainList().contains(domainId) && !plugin.isContainsAllDomain()) { + return; + } SemanticParseInfo parseInfo = semanticQuery.getParseInfo(); - parseInfo.getElementMatches().addAll(queryCtx.getMapInfo().getMatchedElements(domainId)); + if (Objects.nonNull(domainId) && domainId > 0){ + parseInfo.getElementMatches().addAll(queryCtx.getMapInfo().getMatchedElements(domainId)); + } functionCallParseResult.setRequest(queryCtx.getRequest()); Map properties = new HashMap<>(); properties.put(Constants.CONTEXT, functionCallParseResult); parseInfo.setProperties(properties); - parseInfo.setBonus(FUNCTION_BONUS_THRESHOLD); + parseInfo.setScore(FUNCTION_BONUS_THRESHOLD); + parseInfo.setQueryMode(semanticQuery.getQueryMode()); SchemaElement domain = new SchemaElement(); domain.setDomain(domainId); domain.setId(domainId); @@ -95,13 +119,61 @@ public class FunctionBasedParser implements SemanticParser { queryCtx.getCandidateQueries().add(semanticQuery); } - private List getFunctionNames(Long domainId) { + private Set getMatchDomains(QueryContext queryCtx) { + Set result = new HashSet<>(); + Long domainId = queryCtx.getRequest().getDomainId(); + if (Objects.nonNull(domainId) && domainId > 0) { + result.add(domainId); + return result; + } + return queryCtx.getMapInfo().getMatchedDomains(); + } + + private boolean skipFunction(QueryContext queryCtx, FunctionResp functionResp) { + if (Objects.isNull(functionResp) || StringUtils.isBlank(functionResp.getToolSelection())) { + return true; + } + String queryText = queryCtx.getRequest().getQueryText(); + + if (functionResp.getToolSelection().equalsIgnoreCase(DSLQuery.QUERY_MODE) + && queryText.length() < SKIP_DSL_LENGTH) { + log.info("queryText length is :{}, less than the threshold :{}, skip dsl.", queryText.length(), + SKIP_DSL_LENGTH); + return true; + } + return false; + } + + private List getFunctionDO(Long domainId) { + log.info("user decide domain:{}", domainId); + List plugins = PluginManager.getPlugins(); + List functionDOList = plugins.stream().filter(o -> { + if (o.getParseModeConfig() == null) { + return false; + } + if (!CollectionUtils.isEmpty(o.getDomainList())) {//过滤掉没选主题域的插件 + return true; + } + if (domainId == null || domainId <= 0L) { + return true; + } else { + return o.getDomainList().contains(domainId); + } + }).map(o -> { + PluginParseConfig functionCallConfig = JsonUtil.toObject(o.getParseModeConfig(), + PluginParseConfig.class); + return functionCallConfig; + }).collect(Collectors.toList()); + log.info("getFunctionDO:{}", JsonUtil.toString(functionDOList)); + return functionDOList; + } + + private List getFunctionNames(Set matchedDomains) { List plugins = PluginManager.getPlugins(); Set functionNames = plugins.stream() - .filter(entry -> ParseMode.FUNCTION_CALL.equals(entry.getParseMode())) .filter(entry -> { - if (!CollectionUtils.isEmpty(entry.getDomainList())) { - return entry.getDomainList().contains(domainId); + if (!CollectionUtils.isEmpty(entry.getDomainList()) && !CollectionUtils.isEmpty(matchedDomains)) { + return entry.getDomainList().stream().anyMatch(matchedDomains::contains); } return true; } @@ -118,7 +190,7 @@ public class FunctionBasedParser implements SemanticParser { URI requestUrl = UriComponentsBuilder.fromHttpUrl(url).build().encode().toUri(); RestTemplate restTemplate = ContextUtils.getBean(RestTemplate.class); try { - log.info("requestFunction functionReq:{}", functionReq); + log.info("requestFunction functionReq:{}", JsonUtil.toString(functionReq)); ResponseEntity responseEntity = restTemplate.exchange(requestUrl, HttpMethod.POST, entity, FunctionResp.class); log.info("requestFunction responseEntity:{},cost:{}", responseEntity, diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionFiled.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionFiled.java new file mode 100644 index 000000000..5c250ca51 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionFiled.java @@ -0,0 +1,13 @@ +package com.tencent.supersonic.chat.parser.function; + + +import lombok.Data; + +@Data +public class FunctionFiled { + + private String type; + + private String description; + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionReq.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionReq.java index 65c382cc0..ef52541e6 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionReq.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/FunctionReq.java @@ -1,6 +1,8 @@ package com.tencent.supersonic.chat.parser.function; import java.util.List; + +import com.tencent.supersonic.chat.plugin.PluginParseConfig; import lombok.Builder; import lombok.Data; @@ -10,6 +12,6 @@ public class FunctionReq { private String queryText; - private List functionNames; + private List pluginConfigs; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/HeuristicDomainResolver.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/HeuristicDomainResolver.java index db7a486e1..c56508941 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/HeuristicDomainResolver.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/HeuristicDomainResolver.java @@ -1,7 +1,7 @@ package com.tencent.supersonic.chat.parser.function; import com.tencent.supersonic.chat.api.pojo.*; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.api.component.SemanticQuery; import lombok.extern.slf4j.Slf4j; @@ -25,10 +25,10 @@ public class HeuristicDomainResolver implements DomainResolver { Map.Entry maxDomain = domainTypeMap.entrySet().stream() .filter(entry -> domainQueryModes.containsKey(entry.getKey())) .sorted((o1, o2) -> { - int difference = o1.getValue().getCount() - o2.getValue().getCount(); + int difference = o2.getValue().getCount() - o1.getValue().getCount(); if (difference == 0) { - return (int) ((o1.getValue().getMaxSimilarity() - - o2.getValue().getMaxSimilarity()) * 100); + return (int) ((o2.getValue().getMaxSimilarity() + - o1.getValue().getMaxSimilarity()) * 100); } return difference; }).findFirst().orElse(null); @@ -46,7 +46,7 @@ public class HeuristicDomainResolver implements DomainResolver { * @return false will use context domain, true will use other domain , maybe include context domain */ protected static boolean isAllowSwitch(Map domainQueryModes, SchemaMapInfo schemaMap, - ChatContext chatCtx, QueryRequest searchCtx, Long domainId) { + ChatContext chatCtx, QueryReq searchCtx, Long domainId, List restrictiveDomains) { if (!Objects.nonNull(domainId) || domainId <= 0) { return true; } @@ -81,6 +81,9 @@ public class HeuristicDomainResolver implements DomainResolver { } } + if (CollectionUtils.isNotEmpty(restrictiveDomains) && !restrictiveDomains.contains(domainId)) { + return true; + } // if context domain not in schemaMap , will switch if (schemaMap.getMatchedElements(domainId) == null || schemaMap.getMatchedElements(domainId).size() <= 0) { log.info("domainId not in schemaMap "); @@ -118,24 +121,36 @@ public class HeuristicDomainResolver implements DomainResolver { } - public Long resolve(QueryContext queryContext, ChatContext chatCtx) { + public Long resolve(QueryContext queryContext, ChatContext chatCtx, List restrictiveDomains) { Long domainId = queryContext.getRequest().getDomainId(); if (Objects.nonNull(domainId) && domainId > 0) { - return domainId; + if (CollectionUtils.isNotEmpty(restrictiveDomains) && restrictiveDomains.contains(domainId)) { + return domainId; + } else { + return null; + } } SchemaMapInfo mapInfo = queryContext.getMapInfo(); Set matchedDomains = mapInfo.getMatchedDomains(); + if (CollectionUtils.isNotEmpty(restrictiveDomains)) { + matchedDomains = matchedDomains.stream() + .filter(matchedDomain -> restrictiveDomains.contains(matchedDomain)) + .collect(Collectors.toSet()); + } Map domainQueryModes = new HashMap<>(); for (Long matchedDomain : matchedDomains) { domainQueryModes.put(matchedDomain, null); } + if(domainQueryModes.size()==1){ + return domainQueryModes.keySet().stream().findFirst().get(); + } return resolve(domainQueryModes, queryContext, chatCtx, - queryContext.getMapInfo()); + queryContext.getMapInfo(),restrictiveDomains); } public Long resolve(Map domainQueryModes, QueryContext queryContext, - ChatContext chatCtx, SchemaMapInfo schemaMap) { - Long selectDomain = selectDomain(domainQueryModes, queryContext.getRequest(), chatCtx, schemaMap); + ChatContext chatCtx, SchemaMapInfo schemaMap, List restrictiveDomains) { + Long selectDomain = selectDomain(domainQueryModes, queryContext.getRequest(), chatCtx, schemaMap,restrictiveDomains); if (selectDomain > 0) { log.info("selectDomain {} ", selectDomain); return selectDomain; @@ -144,9 +159,9 @@ public class HeuristicDomainResolver implements DomainResolver { return selectDomainBySchemaElementCount(domainQueryModes, schemaMap); } - public Long selectDomain(Map domainQueryModes, QueryRequest queryContext, + public Long selectDomain(Map domainQueryModes, QueryReq queryContext, ChatContext chatCtx, - SchemaMapInfo schemaMap) { + SchemaMapInfo schemaMap, List restrictiveDomains) { // if QueryContext has domainId and in domainQueryModes if (domainQueryModes.containsKey(queryContext.getDomainId())) { log.info("selectDomain from QueryContext [{}]", queryContext.getDomainId()); @@ -155,7 +170,7 @@ public class HeuristicDomainResolver implements DomainResolver { // if ChatContext has domainId and in domainQueryModes if (chatCtx.getParseInfo().getDomainId() > 0) { Long domainId = chatCtx.getParseInfo().getDomainId(); - if (!isAllowSwitch(domainQueryModes, schemaMap, chatCtx, queryContext, domainId)) { + if (!isAllowSwitch(domainQueryModes, schemaMap, chatCtx, queryContext, domainId,restrictiveDomains)) { log.info("selectDomain from ChatContext [{}]", domainId); return domainId; } @@ -163,4 +178,4 @@ public class HeuristicDomainResolver implements DomainResolver { // default 0 return 0L; } -} \ No newline at end of file +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/Parameters.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/Parameters.java new file mode 100644 index 000000000..bd9f88e01 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/function/Parameters.java @@ -0,0 +1,17 @@ +package com.tencent.supersonic.chat.parser.function; + +import lombok.Data; +import java.util.List; +import java.util.Map; + +@Data +public class Parameters { + + //default: object + private String type = "object"; + + private Map properties; + + private List required; + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/llm/LLMTimeEnhancementParse.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/llm/LLMTimeEnhancementParse.java new file mode 100644 index 000000000..0a9440ee9 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/llm/LLMTimeEnhancementParse.java @@ -0,0 +1,49 @@ +package com.tencent.supersonic.chat.parser.llm; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.tencent.supersonic.chat.api.component.SemanticParser; +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.QueryContext; +import com.tencent.supersonic.chat.utils.ChatGptHelper; +import com.tencent.supersonic.common.pojo.DateConf; +import com.tencent.supersonic.common.util.ContextUtils; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class LLMTimeEnhancementParse implements SemanticParser { + + + @Override + public void parse(QueryContext queryContext, ChatContext chatContext) { + log.info("before queryContext:{},chatContext:{}",queryContext,chatContext); + ChatGptHelper chatGptHelper = ContextUtils.getBean(ChatGptHelper.class); + String inferredTime = chatGptHelper.inferredTime(queryContext.getRequest().getQueryText()); + try { + if (!queryContext.getCandidateQueries().isEmpty()) { + for (SemanticQuery query : queryContext.getCandidateQueries()) { + DateConf dateInfo = query.getParseInfo().getDateInfo(); + JSONObject jsonObject = JSON.parseObject(inferredTime); + if (jsonObject.containsKey("date")){ + dateInfo.setDateMode(DateConf.DateMode.BETWEEN); + dateInfo.setStartDate(jsonObject.getString("date")); + dateInfo.setEndDate(jsonObject.getString("date")); + query.getParseInfo().setDateInfo(dateInfo); + }else if (jsonObject.containsKey("start")){ + dateInfo.setDateMode(DateConf.DateMode.BETWEEN); + dateInfo.setStartDate(jsonObject.getString("start")); + dateInfo.setEndDate(jsonObject.getString("end")); + query.getParseInfo().setDateInfo(dateInfo); + } + } + } + }catch (Exception exception){ + log.error("{} parse error,this reason is:{}",LLMTimeEnhancementParse.class.getSimpleName(), (Object) exception.getStackTrace()); + } + + log.info("after queryContext:{},chatContext:{}",queryContext,chatContext); + } + + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/AggregateTypeParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/AggregateTypeParser.java index 9302ab920..4b70804fe 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/AggregateTypeParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/AggregateTypeParser.java @@ -1,22 +1,28 @@ package com.tencent.supersonic.chat.parser.rule; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.AVG; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.COUNT; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.DISTINCT; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.MAX; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.MIN; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.NONE; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.SUM; +import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.TOPN; + import com.tencent.supersonic.chat.api.component.SemanticParser; import com.tencent.supersonic.chat.api.component.SemanticQuery; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.QueryContext; import com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum; - -import java.util.*; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; - -import com.tencent.supersonic.common.util.JsonUtil; import lombok.extern.slf4j.Slf4j; -import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.*; - @Slf4j public class AggregateTypeParser implements SemanticParser { @@ -29,7 +35,7 @@ public class AggregateTypeParser implements SemanticParser { new AbstractMap.SimpleEntry<>(DISTINCT, Pattern.compile("(?i)(uv)")), new AbstractMap.SimpleEntry<>(COUNT, Pattern.compile("(?i)(总数|pv)")), new AbstractMap.SimpleEntry<>(NONE, Pattern.compile("(?i)(明细)")) - ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(k1,k2)->k2)); @Override public void parse(QueryContext queryContext, ChatContext chatContext) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/ContextInheritParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/ContextInheritParser.java index 073a4906c..74a7713f9 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/ContextInheritParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/ContextInheritParser.java @@ -1,17 +1,25 @@ package com.tencent.supersonic.chat.parser.rule; import com.tencent.supersonic.chat.api.component.SemanticParser; -import com.tencent.supersonic.chat.api.pojo.*; -import com.tencent.supersonic.chat.query.rule.metric.MetricDomainQuery; +import com.tencent.supersonic.chat.api.component.SemanticQuery; +import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.QueryContext; +import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; +import com.tencent.supersonic.chat.api.pojo.SchemaElementType; +import com.tencent.supersonic.chat.query.QueryManager; import com.tencent.supersonic.chat.query.rule.RuleSemanticQuery; -import com.tencent.supersonic.common.util.JsonUtil; - -import java.util.*; +import com.tencent.supersonic.chat.query.rule.metric.MetricDomainQuery; +import com.tencent.supersonic.chat.query.rule.metric.MetricEntityQuery; +import com.tencent.supersonic.chat.query.rule.metric.MetricSemanticQuery; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; - import lombok.extern.slf4j.Slf4j; -import org.springframework.util.CollectionUtils; import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; @@ -23,7 +31,8 @@ public class ContextInheritParser implements SemanticParser { new AbstractMap.SimpleEntry<>(DIMENSION, Arrays.asList(DIMENSION, VALUE)), new AbstractMap.SimpleEntry<>(VALUE, Arrays.asList(VALUE, DIMENSION)), new AbstractMap.SimpleEntry<>(ENTITY, Arrays.asList(ENTITY)), - new AbstractMap.SimpleEntry<>(DOMAIN, Arrays.asList(DOMAIN)) + new AbstractMap.SimpleEntry<>(DOMAIN, Arrays.asList(DOMAIN)), + new AbstractMap.SimpleEntry<>(ID, Arrays.asList(ID)) ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); @Override @@ -40,7 +49,9 @@ public class ContextInheritParser implements SemanticParser { for (SchemaElementMatch match : chatContext.getParseInfo().getElementMatches()) { SchemaElementType matchType = match.getElement().getType(); // mutual exclusive element types should not be inherited - if (!containsTypes(elementMatches, MUTUAL_EXCLUSIVE_MAP.get(matchType))) { + RuleSemanticQuery ruleQuery = QueryManager.getRuleQuery(chatContext.getParseInfo().getQueryMode()); + if (!containsTypes(elementMatches, matchType, ruleQuery)) { + match.setMode(SchemaElementMatch.MatchMode.INHERIT); matchesToInherit.add(match); } } @@ -53,22 +64,31 @@ public class ContextInheritParser implements SemanticParser { } } - private boolean containsTypes(List matches, List types) { - return matches.stream().anyMatch(m -> types.contains(m.getElement().getType())); + private boolean containsTypes(List matches, SchemaElementType matchType, + RuleSemanticQuery ruleQuery) { + List types = MUTUAL_EXCLUSIVE_MAP.get(matchType); + + return matches.stream().anyMatch(m -> { + SchemaElementType type = m.getElement().getType(); + if (Objects.nonNull(ruleQuery) && ruleQuery instanceof MetricSemanticQuery + && !(ruleQuery instanceof MetricEntityQuery)) { + return types.contains(type); + } + return type.equals(matchType); + }); } protected boolean shouldInherit(QueryContext queryContext, ChatContext chatContext) { - if (queryContext.getMapInfo().getMatchedElements( - chatContext.getParseInfo().getDomainId()) == null) { + Long contextDomainId = chatContext.getParseInfo().getDomainId(); + if (queryContext.getMapInfo().getMatchedElements(contextDomainId) == null) { return false; } // if candidates have only one MetricDomain mode and context has value filter , count in context - if (queryContext.getCandidateQueries().size() == 1 && (queryContext.getCandidateQueries() - .get(0) instanceof MetricDomainQuery) - && queryContext.getCandidateQueries().get(0).getParseInfo().getDomainId() - .equals(chatContext.getParseInfo().getDomainId()) - && !CollectionUtils.isEmpty(chatContext.getParseInfo().getDimensionFilters())) { + List candidateQueries = queryContext.getCandidateQueries().stream() + .filter(semanticQuery -> semanticQuery.getParseInfo().getDomainId().equals(contextDomainId)).collect( + Collectors.toList()); + if (candidateQueries.size() == 1 && (candidateQueries.get(0) instanceof MetricDomainQuery)) { return true; } else { return queryContext.getCandidateQueries().size() == 0; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/QueryModeParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/QueryModeParser.java index af1d7e0f8..290a7851b 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/QueryModeParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/QueryModeParser.java @@ -6,7 +6,6 @@ import com.tencent.supersonic.chat.query.rule.RuleSemanticQuery; import java.util.*; -import com.tencent.supersonic.common.util.JsonUtil; import lombok.extern.slf4j.Slf4j; @Slf4j diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/TimeRangeParser.java b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/TimeRangeParser.java index fec43ee27..1d20a3208 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/TimeRangeParser.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/parser/rule/TimeRangeParser.java @@ -46,7 +46,7 @@ public class TimeRangeParser implements SemanticParser { for (SemanticQuery query : queryContext.getCandidateQueries()) { query.getParseInfo().setDateInfo(dateConf); } - } else if(QueryManager.containsRuleQuery(chatContext.getParseInfo().getQueryMode())) { + } else if (QueryManager.containsRuleQuery(chatContext.getParseInfo().getQueryMode())) { RuleSemanticQuery semanticQuery = QueryManager.createRuleQuery( chatContext.getParseInfo().getQueryMode()); // inherit parse info from context @@ -64,7 +64,7 @@ public class TimeRangeParser implements SemanticParser { List times = TimeNLPUtil.parse(queryText); if (times.size() > 0) { startDate = times.get(0).getTime(); - }else { + } else { return null; } @@ -133,7 +133,7 @@ public class TimeRangeParser implements SemanticParser { info.setPeriod(Constants.DAY); } days = days * num; - info.setDateMode(DateConf.DateMode.RECENT_UNITS); + info.setDateMode(DateConf.DateMode.RECENT); String text = "近" + num + zhPeriod; if (Strings.isNotEmpty(m.group("periodStr"))) { text = m.group("periodStr"); @@ -175,11 +175,11 @@ public class TimeRangeParser implements SemanticParser { private DateConf getDateConf(Date startDate, Date endDate) { if (startDate == null || endDate == null) { - return null; + return null; } DateConf info = new DateConf(); - info.setDateMode(DateConf.DateMode.BETWEEN_CONTINUOUS); + info.setDateMode(DateConf.DateMode.BETWEEN); info.setStartDate(DATE_FORMAT.format(startDate)); info.setEndDate(DATE_FORMAT.format(endDate)); return info; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/dataobject/PluginDO.java b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/dataobject/PluginDO.java index 30e6a60bf..3cdf0bb14 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/dataobject/PluginDO.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/dataobject/PluginDO.java @@ -53,11 +53,21 @@ public class PluginDO { */ private String updatedBy; + /** + * + */ + private String parseModeConfig; + /** * */ private String config; + /** + * + */ + private String comment; + /** * * @return id @@ -218,6 +228,22 @@ public class PluginDO { this.updatedBy = updatedBy == null ? null : updatedBy.trim(); } + /** + * + * @return parse_mode_config + */ + public String getParseModeConfig() { + return parseModeConfig; + } + + /** + * + * @param parseModeConfig + */ + public void setParseModeConfig(String parseModeConfig) { + this.parseModeConfig = parseModeConfig == null ? null : parseModeConfig.trim(); + } + /** * * @return config @@ -233,4 +259,20 @@ public class PluginDO { public void setConfig(String config) { this.config = config == null ? null : config.trim(); } + + /** + * + * @return comment + */ + public String getComment() { + return comment; + } + + /** + * + * @param comment + */ + public void setComment(String comment) { + this.comment = comment == null ? null : comment.trim(); + } } \ No newline at end of file diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/mapper/PluginDOMapper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/mapper/PluginDOMapper.java index 426be06d8..9f410ed00 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/mapper/PluginDOMapper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/mapper/PluginDOMapper.java @@ -1,6 +1,5 @@ package com.tencent.supersonic.chat.persistence.mapper; - import com.tencent.supersonic.chat.persistence.dataobject.PluginDO; import com.tencent.supersonic.chat.persistence.dataobject.PluginDOExample; import org.apache.ibatis.annotations.Mapper; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatConfigRepository.java b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatConfigRepository.java index 33de13ec6..3a9250e44 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatConfigRepository.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatConfigRepository.java @@ -2,8 +2,8 @@ package com.tencent.supersonic.chat.persistence.repository; import com.tencent.supersonic.chat.config.ChatConfig; -import com.tencent.supersonic.chat.config.ChatConfigFilter; -import com.tencent.supersonic.chat.config.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigFilter; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; import java.util.List; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatQueryRepository.java b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatQueryRepository.java index 4b818c464..3d9dac6a4 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatQueryRepository.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/ChatQueryRepository.java @@ -2,17 +2,17 @@ package com.tencent.supersonic.chat.persistence.repository; import com.github.pagehelper.PageInfo; import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.persistence.dataobject.ChatQueryDO; -import com.tencent.supersonic.chat.api.pojo.response.QueryResponse; +import com.tencent.supersonic.chat.api.pojo.response.QueryResp; import com.tencent.supersonic.chat.api.pojo.request.PageQueryInfoReq; public interface ChatQueryRepository { - PageInfo getChatQuery(PageQueryInfoReq pageQueryInfoCommend, long chatId); + PageInfo getChatQuery(PageQueryInfoReq pageQueryInfoCommend, long chatId); - void createChatQuery(QueryResult queryResult, QueryRequest queryContext, ChatContext chatCtx); + void createChatQuery(QueryResult queryResult, ChatContext chatCtx); ChatQueryDO getLastChatQuery(long chatId); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatConfigRepositoryImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatConfigRepositoryImpl.java index cec064a1f..2670d8be2 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatConfigRepositoryImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatConfigRepositoryImpl.java @@ -1,9 +1,9 @@ package com.tencent.supersonic.chat.persistence.repository.impl; import com.tencent.supersonic.chat.config.ChatConfig; -import com.tencent.supersonic.chat.config.ChatConfigFilter; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigFilter; import com.tencent.supersonic.chat.config.ChatConfigFilterInternal; -import com.tencent.supersonic.chat.config.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; import com.tencent.supersonic.chat.persistence.dataobject.ChatConfigDO; import com.tencent.supersonic.chat.persistence.repository.ChatConfigRepository; import com.tencent.supersonic.chat.utils.ChatConfigHelper; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatQueryRepositoryImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatQueryRepositoryImpl.java index a5e87980b..dad207009 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatQueryRepositoryImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/persistence/repository/impl/ChatQueryRepositoryImpl.java @@ -3,12 +3,12 @@ package com.tencent.supersonic.chat.persistence.repository.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.tencent.supersonic.chat.api.pojo.ChatContext; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.persistence.dataobject.ChatQueryDO; import com.tencent.supersonic.chat.persistence.dataobject.ChatQueryDOExample; import com.tencent.supersonic.chat.persistence.dataobject.ChatQueryDOExample.Criteria; -import com.tencent.supersonic.chat.api.pojo.response.QueryResponse; +import com.tencent.supersonic.chat.api.pojo.response.QueryResp; import com.tencent.supersonic.chat.api.pojo.request.PageQueryInfoReq; import com.tencent.supersonic.chat.persistence.mapper.ChatQueryDOMapper; import com.tencent.supersonic.chat.persistence.repository.ChatQueryRepository; @@ -35,7 +35,7 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { } @Override - public PageInfo getChatQuery(PageQueryInfoReq pageQueryInfoCommend, long chatId) { + public PageInfo getChatQuery(PageQueryInfoReq pageQueryInfoCommend, long chatId) { ChatQueryDOExample example = new ChatQueryDOExample(); example.setOrderByClause("question_id desc"); Criteria criteria = example.createCriteria(); @@ -46,7 +46,7 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { pageQueryInfoCommend.getPageSize()) .doSelectPageInfo(() -> chatQueryDOMapper.selectByExampleWithBLOBs(example)); - PageInfo chatQueryVOPageInfo = PageUtils.pageInfo2PageInfoVo(pageInfo); + PageInfo chatQueryVOPageInfo = PageUtils.pageInfo2PageInfoVo(pageInfo); chatQueryVOPageInfo.setList( pageInfo.getList().stream().map(this::convertTo) .sorted(Comparator.comparingInt(o -> o.getQuestionId().intValue())) @@ -54,8 +54,8 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { return chatQueryVOPageInfo; } - private QueryResponse convertTo(ChatQueryDO chatQueryDO) { - QueryResponse queryResponse = new QueryResponse(); + private QueryResp convertTo(ChatQueryDO chatQueryDO) { + QueryResp queryResponse = new QueryResp(); BeanUtils.copyProperties(chatQueryDO, queryResponse); QueryResult queryResult = JsonUtil.toObject(chatQueryDO.getQueryResult(), QueryResult.class); queryResult.setQueryId(chatQueryDO.getQuestionId()); @@ -64,16 +64,16 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository { } @Override - public void createChatQuery(QueryResult queryResult, QueryRequest queryRequest, ChatContext chatCtx) { + public void createChatQuery(QueryResult queryResult, ChatContext chatCtx) { ChatQueryDO chatQueryDO = new ChatQueryDO(); - chatQueryDO.setChatId(Long.valueOf(queryRequest.getChatId())); + chatQueryDO.setChatId(Long.valueOf(chatCtx.getChatId())); chatQueryDO.setCreateTime(new java.util.Date()); - chatQueryDO.setUserName(queryRequest.getUser().getName()); + chatQueryDO.setUserName(chatCtx.getUser()); chatQueryDO.setQueryState(queryResult.getQueryState().ordinal()); - chatQueryDO.setQueryText(queryRequest.getQueryText()); + chatQueryDO.setQueryText(chatCtx.getQueryText()); chatQueryDO.setQueryResult(JsonUtil.toString(queryResult)); chatQueryDOMapper.insert(chatQueryDO); - ChatQueryDO lastChatQuery = getLastChatQuery(queryRequest.getChatId()); + ChatQueryDO lastChatQuery = getLastChatQuery(chatCtx.getChatId()); Long queryId = lastChatQuery.getQuestionId(); queryResult.setQueryId(queryId); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/Plugin.java b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/Plugin.java index 469e11bfc..5ce89f197 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/Plugin.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/Plugin.java @@ -1,37 +1,58 @@ package com.tencent.supersonic.chat.plugin; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; import com.tencent.supersonic.chat.parser.ParseMode; import com.tencent.supersonic.common.pojo.RecordInfo; import lombok.Data; - +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Data public class Plugin extends RecordInfo { private Long id; - //plugin type WEB_PAGE WEB_SERVICE + /*** + * plugin type WEB_PAGE WEB_SERVICE + */ private String type; - private List domainList; + private List domainList = Lists.newArrayList(); - //description, for parsing + /** + * description, for parsing + */ private String pattern; - //parse + /** + * parse + */ private ParseMode parseMode; + private String parseModeConfig; + private String name; - //config for different plugin type + /** + * config for different plugin type + */ private String config; - public List getPatterns() { - return Stream.of(getPattern().split("\\|")).collect(Collectors.toList()); + private String comment; + + public List getExampleQuestionList() { + if (StringUtils.isNotBlank(parseModeConfig)) { + PluginParseConfig pluginParseConfig = JSONObject.parseObject(parseModeConfig, PluginParseConfig.class); + return pluginParseConfig.getExamples(); + } + return Lists.newArrayList(); + } + + public boolean isContainsAllDomain() { + return CollectionUtils.isNotEmpty(domainList) && domainList.contains(-1L); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginManager.java b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginManager.java index 57540ebb5..67bb6bb18 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginManager.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginManager.java @@ -16,6 +16,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.util.Strings; import org.springframework.context.event.EventListener; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; @@ -40,7 +41,8 @@ public class PluginManager { public static List getPlugins() { PluginService pluginService = ContextUtils.getBean(PluginService.class); - List pluginList = pluginService.getPluginList(); + List pluginList = pluginService.getPluginList().stream().filter(plugin -> + CollectionUtils.isNotEmpty(plugin.getDomainList())).collect(Collectors.toList()); pluginList.addAll(internalPluginMap.values()); return new ArrayList<>(pluginList); } @@ -48,7 +50,7 @@ public class PluginManager { @EventListener public void addPlugin(PluginAddEvent pluginAddEvent) { Plugin plugin = pluginAddEvent.getPlugin(); - if (ParseMode.EMBEDDING_RECALL.equals(plugin.getParseMode())) { + if (CollectionUtils.isNotEmpty(plugin.getExampleQuestionList())) { requestEmbeddingPluginAdd(convert(Lists.newArrayList(plugin))); } } @@ -57,10 +59,10 @@ public class PluginManager { public void updatePlugin(PluginUpdateEvent pluginUpdateEvent) { Plugin oldPlugin = pluginUpdateEvent.getOldPlugin(); Plugin newPlugin = pluginUpdateEvent.getNewPlugin(); - if (ParseMode.EMBEDDING_RECALL.equals(oldPlugin.getParseMode())) { + if (CollectionUtils.isNotEmpty(oldPlugin.getExampleQuestionList())) { requestEmbeddingPluginDelete(getEmbeddingId(Lists.newArrayList(oldPlugin))); } - if (ParseMode.EMBEDDING_RECALL.equals(newPlugin.getParseMode())) { + if (CollectionUtils.isNotEmpty(newPlugin.getExampleQuestionList())) { requestEmbeddingPluginAdd(convert(Lists.newArrayList(newPlugin))); } } @@ -68,29 +70,30 @@ public class PluginManager { @EventListener public void delPlugin(PluginAddEvent pluginAddEvent) { Plugin plugin = pluginAddEvent.getPlugin(); - if (ParseMode.EMBEDDING_RECALL.equals(plugin.getParseMode())) { + if (CollectionUtils.isNotEmpty(plugin.getExampleQuestionList())) { requestEmbeddingPluginDelete(getEmbeddingId(Lists.newArrayList(plugin))); } - } public void requestEmbeddingPluginDelete(Set ids) { - if(CollectionUtils.isEmpty(ids)){ + if (CollectionUtils.isEmpty(ids)) { return; } doRequest(embeddingConfig.getDeletePath(), JSONObject.toJSONString(ids)); } - - public void requestEmbeddingPluginAdd(List> maps) { - if(CollectionUtils.isEmpty(maps)){ + public void requestEmbeddingPluginAdd(List> maps) { + if (CollectionUtils.isEmpty(maps)) { return; } - doRequest(embeddingConfig.getAddPath(), JSONObject.toJSONString(maps)); + doRequest(embeddingConfig.getAddPath(), JSONObject.toJSONString(maps)); } public void doRequest(String path, String jsonBody) { - String url = embeddingConfig.getUrl()+ path; + if (Strings.isEmpty(embeddingConfig.getUrl())) { + return; + } + String url = embeddingConfig.getUrl() + path; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setLocation(URI.create(url)); @@ -99,7 +102,8 @@ public class PluginManager { HttpEntity entity = new HttpEntity<>(jsonBody, headers); log.info("[embedding] equest body :{}, url:{}", jsonBody, url); ResponseEntity responseEntity = - restTemplate.exchange(requestUrl, HttpMethod.POST, entity, new ParameterizedTypeReference() {}); + restTemplate.exchange(requestUrl, HttpMethod.POST, entity, new ParameterizedTypeReference() { + }); log.info("[embedding] result body:{}", responseEntity); } @@ -111,7 +115,7 @@ public class PluginManager { } public EmbeddingResp recognize(String embeddingText) { - String url = embeddingConfig.getUrl()+ embeddingConfig.getRecognizePath() + "?n_results=" + embeddingConfig.getNResult(); + String url = embeddingConfig.getUrl() + embeddingConfig.getRecognizePath() + "?n_results=" + embeddingConfig.getNResult(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setLocation(URI.create(url)); @@ -121,10 +125,11 @@ public class PluginManager { HttpEntity entity = new HttpEntity<>(jsonBody, headers); log.info("[embedding] request body:{}, url:{}", jsonBody, url); ResponseEntity> embeddingResponseEntity = - restTemplate.exchange(requestUrl, HttpMethod.POST, entity, new ParameterizedTypeReference>() {}); - log.info("[embedding] recognize result body:{}",embeddingResponseEntity); + restTemplate.exchange(requestUrl, HttpMethod.POST, entity, new ParameterizedTypeReference>() { + }); + log.info("[embedding] recognize result body:{}", embeddingResponseEntity); List embeddingResps = embeddingResponseEntity.getBody(); - if(CollectionUtils.isNotEmpty(embeddingResps)){ + if (CollectionUtils.isNotEmpty(embeddingResps)) { for (EmbeddingResp embeddingResp : embeddingResps) { List embeddingRetrievals = embeddingResp.getRetrieval(); for (RecallRetrieval embeddingRetrieval : embeddingRetrievals) { @@ -136,13 +141,13 @@ public class PluginManager { throw new RuntimeException("get embedding result failed"); } - public List> convert(List plugins){ + public List> convert(List plugins) { List> maps = Lists.newArrayList(); - for(Plugin plugin : plugins){ - List patterns = plugin.getPatterns(); + for (Plugin plugin : plugins) { + List exampleQuestions = plugin.getExampleQuestionList(); int num = 0; - for(String pattern : patterns){ - Map map = new HashMap<>(); + for (String pattern : exampleQuestions) { + Map map = new HashMap<>(); map.put("preset_query_id", generateUniqueEmbeddingId(num, plugin.getId())); map.put("preset_query", pattern); maps.add(map); @@ -155,7 +160,7 @@ public class PluginManager { private Set getEmbeddingId(List plugins) { Set embeddingIdSet = new HashSet<>(); for (Map map : convert(plugins)) { - embeddingIdSet.addAll(map.keySet()); + embeddingIdSet.add(map.get("preset_query_id")); } return embeddingIdSet; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseConfig.java b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseConfig.java new file mode 100644 index 000000000..d6482eadb --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseConfig.java @@ -0,0 +1,21 @@ +package com.tencent.supersonic.chat.plugin; + + +import com.tencent.supersonic.chat.parser.function.Parameters; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class PluginParseConfig implements Serializable { + + private String name; + + private String description; + + public Parameters parameters; + + public List examples; + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseResult.java b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseResult.java index ba7a4a9c7..ab93d2908 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseResult.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/plugin/PluginParseResult.java @@ -1,11 +1,12 @@ package com.tencent.supersonic.chat.plugin; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import lombok.Data; @Data public class PluginParseResult { private Plugin plugin; - private QueryRequest request; + private QueryReq request; + private double distance; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/HeuristicQuerySelector.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/HeuristicQuerySelector.java index 82ef74840..cb6602263 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/HeuristicQuerySelector.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/HeuristicQuerySelector.java @@ -5,39 +5,46 @@ import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; import com.tencent.supersonic.chat.api.pojo.SchemaElementType; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.common.pojo.Constants; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; + +import java.util.*; + import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; @Slf4j public class HeuristicQuerySelector implements QuerySelector { - @Override - public SemanticQuery select(List candidateQueries) { - double maxScore = 0; - SemanticQuery pickedQuery = null; - if (CollectionUtils.isNotEmpty(candidateQueries) && candidateQueries.size() == 1) { - return candidateQueries.get(0); - } - for (SemanticQuery query : candidateQueries) { - SemanticParseInfo semanticParse = query.getParseInfo(); - double score = computeScore(semanticParse); - if (score > maxScore) { - maxScore = score; - pickedQuery = query; - } - log.info("candidate query (domain={}, queryMode={}) with score={}", - semanticParse.getDomainName(), semanticParse.getQueryMode(), score); - } + private static final double MATCH_INHERIT_PENALTY = 0.5; + private static final double MATCH_CURRENT_REWORD = 2; + private static final double CANDIDATE_THRESHOLD = 0.2; - return pickedQuery; + @Override + public List select(List candidateQueries) { + List selectedQueries = new ArrayList<>(); + + if (CollectionUtils.isNotEmpty(candidateQueries) && candidateQueries.size() == 1) { + selectedQueries.addAll(candidateQueries); + } else { + OptionalDouble maxScoreOp = candidateQueries.stream().mapToDouble( + q -> computeScore(q.getParseInfo())).max(); + if (maxScoreOp.isPresent()) { + double maxScore = maxScoreOp.getAsDouble(); + + candidateQueries.stream().forEach(query -> { + SemanticParseInfo semanticParse = query.getParseInfo(); + if ((maxScore - semanticParse.getScore()) / maxScore <= CANDIDATE_THRESHOLD) { + selectedQueries.add(query); + } + log.info("candidate query (domain={}, queryMode={}) with score={}", + semanticParse.getDomainName(), semanticParse.getQueryMode(), semanticParse.getScore()); + }); + } + } + return selectedQueries; } private double computeScore(SemanticParseInfo semanticParse) { - double score = 0; + double totalScore = 0; Map maxSimilarityMatch = new HashMap<>(); for (SchemaElementMatch match : semanticParse.getElementMatches()) { @@ -49,13 +56,19 @@ public class HeuristicQuerySelector implements QuerySelector { } for (SchemaElementMatch match : maxSimilarityMatch.values()) { - score += - Optional.ofNullable(match.getDetectWord()).orElse(Constants.EMPTY).length() * match.getSimilarity(); + double matchScore = Optional.ofNullable(match.getDetectWord()).orElse(Constants.EMPTY).length() * match.getSimilarity(); + if (match.equals(SchemaElementMatch.MatchMode.INHERIT)) { + matchScore *= MATCH_INHERIT_PENALTY; + } else { + matchScore *= MATCH_CURRENT_REWORD; + } + totalScore += matchScore; } - // bonus is a special construct to control the final score - score += semanticParse.getBonus(); + // original score in parse info acts like an extra bonus + totalScore += semanticParse.getScore(); + semanticParse.setScore(totalScore); - return score; + return totalScore; } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/QueryManager.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/QueryManager.java index 4cd897fb3..9b808f7f1 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/QueryManager.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/QueryManager.java @@ -3,6 +3,9 @@ package com.tencent.supersonic.chat.query; import com.tencent.supersonic.chat.api.component.SemanticQuery; import com.tencent.supersonic.chat.query.plugin.PluginSemanticQuery; import com.tencent.supersonic.chat.query.rule.RuleSemanticQuery; +import com.tencent.supersonic.chat.query.rule.entity.EntitySemanticQuery; +import com.tencent.supersonic.chat.query.rule.metric.MetricSemanticQuery; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -22,6 +25,14 @@ public class QueryManager { } } + public static SemanticQuery createQuery(String queryMode) { + if (containsRuleQuery(queryMode)) { + return createRuleQuery(queryMode); + } else { + return createPluginQuery(queryMode); + } + } + public static RuleSemanticQuery createRuleQuery(String queryMode) { RuleSemanticQuery semanticQuery = ruleQueryMap.get(queryMode); if (Objects.isNull(semanticQuery)) { @@ -45,7 +56,6 @@ public class QueryManager { throw new RuntimeException("no supported queryMode :" + queryMode); } } - public static boolean containsRuleQuery(String queryMode) { if (queryMode == null) { return false; @@ -53,6 +63,27 @@ public class QueryManager { return ruleQueryMap.containsKey(queryMode); } + public static boolean isMetricQuery(String queryMode) { + if (queryMode == null || !ruleQueryMap.containsKey(queryMode)) { + return false; + } + return ruleQueryMap.get(queryMode) instanceof MetricSemanticQuery; + } + + public static boolean isEntityQuery(String queryMode) { + if (queryMode == null || !ruleQueryMap.containsKey(queryMode)) { + return false; + } + return ruleQueryMap.get(queryMode) instanceof EntitySemanticQuery; + } + + public static RuleSemanticQuery getRuleQuery(String queryMode) { + if (queryMode == null) { + return null; + } + return ruleQueryMap.get(queryMode); + } + public static List getRuleQueries() { return new ArrayList<>(ruleQueryMap.values()); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/QuerySelector.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/QuerySelector.java index dcc8ae79c..906938548 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/QuerySelector.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/QuerySelector.java @@ -9,5 +9,5 @@ import java.util.List; **/ public interface QuerySelector { - SemanticQuery select(List candidateQueries); + List select(List candidateQueries); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/ParamOption.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/ParamOption.java new file mode 100644 index 000000000..4e786eefb --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/ParamOption.java @@ -0,0 +1,37 @@ +package com.tencent.supersonic.chat.query.plugin; + +import lombok.Data; + +@Data +public class ParamOption { + + private ParamType paramType; + + private OptionType optionType; + + private String key; + + private String name; + + private String keyAlias; + + private Long domainId; + + private Long elementId; + + private Object value; + + /** + * CUSTOM: the value is specified by the user + * SEMANTIC: the value of element + * FORWARD: only forward + */ + public enum ParamType { + CUSTOM, SEMANTIC, FORWARD + } + + public enum OptionType { + REQUIRED, OPTIONAL + } + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBase.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBase.java index a9aec8bf4..dd26c2ff0 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBase.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBase.java @@ -1,22 +1,14 @@ package com.tencent.supersonic.chat.query.plugin; +import com.google.common.collect.Lists; import lombok.Data; - -import java.util.HashMap; -import java.util.Map; +import java.util.List; @Data public class WebBase { private String url; - //key, id of schema element - private Map params = new HashMap<>(); - - //key, value of shcema element - private Map valueParams = new HashMap<>(); - - //only forward - private Map forwardParam = new HashMap<>(); + private List paramOptions = Lists.newArrayList(); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBaseResult.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBaseResult.java new file mode 100644 index 000000000..8ba6cde74 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/WebBaseResult.java @@ -0,0 +1,14 @@ +package com.tencent.supersonic.chat.query.plugin; + +import com.google.common.collect.Lists; +import lombok.Data; +import java.util.List; + +@Data +public class WebBaseResult { + + private String url; + + private List params = Lists.newArrayList(); + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/dsl/DSLQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/dsl/DSLQuery.java index 16d1de9d9..a690c32c6 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/dsl/DSLQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/dsl/DSLQuery.java @@ -62,8 +62,7 @@ public class DSLQuery extends PluginSemanticQuery { @Override public QueryResult execute(User user) { - PluginParseResult functionCallParseResult = (PluginParseResult) parseInfo.getProperties() - .get(Constants.CONTEXT); + PluginParseResult functionCallParseResult =JsonUtil.toObject(JsonUtil.toString(parseInfo.getProperties().get(Constants.CONTEXT)),PluginParseResult.class); Long domainId = parseInfo.getDomainId(); LLMResp llmResp = requestLLM(functionCallParseResult, domainId); if (Objects.isNull(llmResp)) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageQuery.java index 7c981d808..f28b796fd 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageQuery.java @@ -1,15 +1,19 @@ package com.tencent.supersonic.chat.query.plugin.webpage; +import com.google.common.collect.Lists; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.*; import com.tencent.supersonic.chat.api.pojo.response.EntityInfo; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.api.pojo.response.QueryState; -import com.tencent.supersonic.chat.config.ChatConfigRich; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; import com.tencent.supersonic.chat.plugin.Plugin; +import com.tencent.supersonic.chat.plugin.PluginParseResult; import com.tencent.supersonic.chat.query.QueryManager; +import com.tencent.supersonic.chat.query.plugin.ParamOption; import com.tencent.supersonic.chat.query.plugin.PluginSemanticQuery; import com.tencent.supersonic.chat.query.plugin.WebBase; +import com.tencent.supersonic.chat.query.plugin.WebBaseResult; import com.tencent.supersonic.chat.service.ConfigService; import com.tencent.supersonic.chat.service.SemanticService; import com.tencent.supersonic.common.pojo.Constants; @@ -18,10 +22,9 @@ import com.tencent.supersonic.common.util.JsonUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; + +import java.util.*; +import java.util.stream.Collectors; @Slf4j @Component @@ -44,13 +47,14 @@ public class WebPageQuery extends PluginSemanticQuery { QueryResult queryResult = new QueryResult(); queryResult.setQueryMode(QUERY_MODE); Map properties = parseInfo.getProperties(); - Plugin plugin = (Plugin) properties.get(Constants.CONTEXT); - WebPageResponse webPageResponse = buildResponse(plugin); + PluginParseResult pluginParseResult = JsonUtil.toObject(JsonUtil.toString(properties.get(Constants.CONTEXT)), PluginParseResult.class); + WebPageResponse webPageResponse = buildResponse(pluginParseResult.getPlugin()); queryResult.setResponse(webPageResponse); if (parseInfo.getDomainId() != null && parseInfo.getDomainId() > 0 - && parseInfo.getEntity() != null && parseInfo.getEntity() > 0) { - ChatConfigRich chatConfigRichResp = configService.getConfigRichInfo(parseInfo.getDomainId()); - updateSemanticParse(chatConfigRichResp, parseInfo.getEntity()); + && parseInfo.getEntity() != null && Objects.nonNull(parseInfo.getEntity().getId()) + && parseInfo.getEntity().getId() > 0) { + ChatConfigRichResp chatConfigRichResp = configService.getConfigRichInfo(parseInfo.getDomainId()); + updateSemanticParse(chatConfigRichResp); EntityInfo entityInfo = ContextUtils.getBean(SemanticService.class).getEntityInfo(parseInfo, user); queryResult.setEntityInfo(entityInfo); } else { @@ -60,8 +64,7 @@ public class WebPageQuery extends PluginSemanticQuery { return queryResult; } - private void updateSemanticParse(ChatConfigRich chatConfigRichResp, Long entityId) { - parseInfo.setEntity(entityId); + private void updateSemanticParse(ChatConfigRichResp chatConfigRichResp) { SchemaElement domain = new SchemaElement(); domain.setId(chatConfigRichResp.getDomainId()); domain.setName(chatConfigRichResp.getDomainName()); @@ -74,35 +77,43 @@ public class WebPageQuery extends PluginSemanticQuery { webPageResponse.setPluginId(plugin.getId()); webPageResponse.setPluginType(plugin.getType()); WebBase webPage = JsonUtil.toObject(plugin.getConfig(), WebBase.class); - fillWebPage(webPage); - webPageResponse.setWebPage(webPage); + WebBaseResult webBaseResult = buildWebPageResult(webPage); + webPageResponse.setWebPage(webBaseResult); return webPageResponse; } - private void fillWebPage(WebBase webPage) { - List schemaElementMatchList = parseInfo.getElementMatches(); + private WebBaseResult buildWebPageResult(WebBase webPage) { + WebBaseResult webBaseResult = new WebBaseResult(); + webBaseResult.setUrl(webPage.getUrl()); + Map elementValueMap = getElementMap(); + if (!CollectionUtils.isEmpty(webPage.getParamOptions()) && !CollectionUtils.isEmpty(elementValueMap)) { + for (ParamOption paramOption : webPage.getParamOptions()) { + if (!ParamOption.ParamType.SEMANTIC.equals(paramOption.getParamType())) { + continue; + } + String elementId = String.valueOf(paramOption.getElementId()); + Object elementValue = elementValueMap.get(elementId); + paramOption.setValue(elementValue); + } + } + webBaseResult.setParams(webPage.getParamOptions()); + return webBaseResult; + } + + private Map getElementMap() { Map elementValueMap = new HashMap<>(); - if (!CollectionUtils.isEmpty(schemaElementMatchList) && !CollectionUtils.isEmpty(webPage.getParams()) ) { + List schemaElementMatchList = parseInfo.getElementMatches(); + if (!CollectionUtils.isEmpty(schemaElementMatchList)) { schemaElementMatchList.stream() .filter(schemaElementMatch -> - SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType())) + SchemaElementType.VALUE.equals(schemaElementMatch.getElement().getType()) + || SchemaElementType.ID.equals(schemaElementMatch.getElement().getType())) .sorted(Comparator.comparingDouble(SchemaElementMatch::getSimilarity)) .forEach(schemaElementMatch -> elementValueMap.put(String.valueOf(schemaElementMatch.getElement().getId()), schemaElementMatch.getWord())); } - if (!CollectionUtils.isEmpty(parseInfo.getDimensionFilters())) { - parseInfo.getDimensionFilters().forEach( - filter -> elementValueMap.put(String.valueOf(filter.getElementID()), filter.getValue()) - ); - } - Map params = webPage.getParams(); - for (Map.Entry entry : params.entrySet()) { - String key = entry.getKey(); - String elementId = String.valueOf(entry.getValue()); - Object elementValue = elementValueMap.get(elementId); - webPage.getValueParams().put(key, elementValue); - } + return elementValueMap; } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageResponse.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageResponse.java index 6c75a37f9..8a728ae91 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageResponse.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webpage/WebPageResponse.java @@ -1,6 +1,7 @@ package com.tencent.supersonic.chat.query.plugin.webpage; import com.tencent.supersonic.chat.query.plugin.WebBase; +import com.tencent.supersonic.chat.query.plugin.WebBaseResult; import lombok.Data; import java.util.List; @@ -15,8 +16,8 @@ public class WebPageResponse { private String description; - private WebBase webPage; + private WebBaseResult webPage; - private List moreWebPage; + private List moreWebPage; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webservice/WebServiceQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webservice/WebServiceQuery.java index d9ebe1f95..427f7b5db 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webservice/WebServiceQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/plugin/webservice/WebServiceQuery.java @@ -1,22 +1,28 @@ package com.tencent.supersonic.chat.query.plugin.webservice; +import com.alibaba.fastjson.JSON; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.api.pojo.response.QueryState; import com.tencent.supersonic.chat.plugin.Plugin; import com.tencent.supersonic.chat.plugin.PluginParseResult; import com.tencent.supersonic.chat.query.QueryManager; +import com.tencent.supersonic.chat.query.plugin.ParamOption; import com.tencent.supersonic.chat.query.plugin.PluginSemanticQuery; import com.tencent.supersonic.chat.query.plugin.WebBase; import com.tencent.supersonic.common.pojo.Constants; import com.tencent.supersonic.common.pojo.QueryColumn; import com.tencent.supersonic.common.util.*; +import java.net.URI; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.calcite.sql.parser.SqlParseException; +import org.springframework.http.*; import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; import java.util.HashMap; import java.util.Map; @@ -27,7 +33,7 @@ public class WebServiceQuery extends PluginSemanticQuery { public static String QUERY_MODE = "WEB_SERVICE"; - private S2ThreadContext s2ThreadContext; + private RestTemplate restTemplate; public WebServiceQuery() { QueryManager.register(this); @@ -43,13 +49,9 @@ public class WebServiceQuery extends PluginSemanticQuery { QueryResult queryResult = new QueryResult(); queryResult.setQueryMode(QUERY_MODE); Map properties = parseInfo.getProperties(); - PluginParseResult pluginParseResult = (PluginParseResult) properties.get(Constants.CONTEXT); + PluginParseResult pluginParseResult =JsonUtil.toObject(JsonUtil.toString(properties.get(Constants.CONTEXT)),PluginParseResult.class); WebServiceResponse webServiceResponse = buildResponse(pluginParseResult); - Object object = webServiceResponse.getResult(); - Map data=JsonUtil.toMap(JsonUtil.toString(object),String.class,Object.class); - queryResult.setQueryResults((List>) data.get("resultList")); - queryResult.setQueryColumns((List) data.get("columns")); - //queryResult.setResponse(webServiceResponse); + queryResult.setResponse(webServiceResponse); queryResult.setQueryState(QueryState.SUCCESS); parseInfo.setProperties(null); return queryResult; @@ -60,22 +62,22 @@ public class WebServiceQuery extends PluginSemanticQuery { Plugin plugin = pluginParseResult.getPlugin(); WebBase webBase = JsonUtil.toObject(plugin.getConfig(), WebBase.class); webServiceResponse.setWebBase(webBase); - //http todo - s2ThreadContext = ContextUtils.getBean(S2ThreadContext.class); - String authHeader = s2ThreadContext.get().getToken(); - log.info("authHeader:{}", authHeader); + List paramOptions = webBase.getParamOptions(); + Map params = new HashMap<>(); + paramOptions.forEach(o -> params.put(o.getKey(), o.getValue())); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(JSON.toJSONString(params), headers); + URI requestUrl = UriComponentsBuilder.fromHttpUrl(webBase.getUrl()).build().encode().toUri(); + ResponseEntity responseEntity = null; + Object objectResponse = null; + restTemplate = ContextUtils.getBean(RestTemplate.class); try { - Map headers = new HashMap<>(); - headers.put("Authorization", authHeader); - Map params = new HashMap<>(); - params.put("queryText", pluginParseResult.getRequest().getQueryText()); - HttpClientResult httpClientResult = HttpClientUtils.doGet(webBase.getUrl(), headers, params); - log.info(" response body:{}", httpClientResult.getContent()); - Map result = JsonUtil.toMap(JsonUtil.toString(httpClientResult.getContent()), String.class, Object.class); - log.info(" result:{}", result); - Map data = JsonUtil.toMap(JsonUtil.toString(result.get("data")), String.class, Object.class); - log.info(" data:{}", data); - webServiceResponse.setResult(data); + responseEntity = restTemplate.exchange(requestUrl, HttpMethod.POST, entity, Object.class); + objectResponse = responseEntity.getBody(); + log.info("objectResponse:{}", objectResponse); + Map response = JsonUtil.objectToMap(objectResponse); + webServiceResponse.setResult(response); } catch (Exception e) { log.info("Exception:{}", e.getMessage()); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/QueryMatcher.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/QueryMatcher.java index ce74dd3a7..f10a524a7 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/QueryMatcher.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/QueryMatcher.java @@ -26,8 +26,6 @@ public class QueryMatcher { private boolean supportOrderBy; private List orderByTypes = Arrays.asList(AggregateTypeEnum.MAX, AggregateTypeEnum.MIN, AggregateTypeEnum.TOPN); - private Long FREQUENCY = 9999999L; - private double SIMILARITY = 1.0; public QueryMatcher() { for (SchemaElementType type : SchemaElementType.values()) { @@ -52,11 +50,10 @@ public class QueryMatcher { * @return a list of all matched schema elements, * empty list if no matches can be found */ - public List match(List candidateElementMatches, QueryFilters queryFilters) { + public List match(List candidateElementMatches) { List elementMatches = new ArrayList<>(); - List schemaElementMatchWithQueryFilter = addSchemaElementMatch(candidateElementMatches, queryFilters); HashMap schemaElementTypeCount = new HashMap<>(); - for (SchemaElementMatch schemaElementMatch : schemaElementMatchWithQueryFilter) { + for (SchemaElementMatch schemaElementMatch : candidateElementMatches) { SchemaElementType schemaElementType = schemaElementMatch.getElement().getType(); if (schemaElementTypeCount.containsKey(schemaElementType)) { schemaElementTypeCount.put(schemaElementType, schemaElementTypeCount.get(schemaElementType) + 1); @@ -75,7 +72,7 @@ public class QueryMatcher { } // add element match if its element type is not declared as unused - for (SchemaElementMatch elementMatch : schemaElementMatchWithQueryFilter) { + for (SchemaElementMatch elementMatch : candidateElementMatches) { QueryMatchOption elementOption = elementOptionMap.get(elementMatch.getElement().getType()); if (Objects.nonNull(elementOption) && !elementOption.getSchemaElementOption() .equals(QueryMatchOption.OptionType.UNUSED)) { @@ -86,32 +83,6 @@ public class QueryMatcher { return elementMatches; } - private List addSchemaElementMatch(List candidateElementMatches, QueryFilters queryFilter) { - List schemaElementMatchWithQueryFilter = new ArrayList<>(candidateElementMatches); - if (queryFilter == null || CollectionUtils.isEmpty(queryFilter.getFilters())) { - return schemaElementMatchWithQueryFilter; - } - QueryMatchOption queryMatchOption = elementOptionMap.get(SchemaElementType.VALUE); - if (queryMatchOption != null && QueryMatchOption.OptionType.REQUIRED.equals(queryMatchOption.getSchemaElementOption())) { - for (QueryFilter filter : queryFilter.getFilters()) { - SchemaElement element = SchemaElement.builder() - .id(filter.getElementID()) - .name(String.valueOf(filter.getValue())) - .type(SchemaElementType.VALUE) - .build(); - SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder() - .element(element) - .frequency(FREQUENCY) - .word(String.valueOf(filter.getValue())) - .similarity(SIMILARITY) - .detectWord(Constants.EMPTY) - .build(); - schemaElementMatchWithQueryFilter.add(schemaElementMatch); - } - } - return schemaElementMatchWithQueryFilter; - } - private int getCount(HashMap schemaElementTypeCount, SchemaElementType schemaElementType) { if (schemaElementTypeCount.containsKey(schemaElementType)) { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/RuleSemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/RuleSemanticQuery.java index 173eb2b6e..5a105b436 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/RuleSemanticQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/RuleSemanticQuery.java @@ -9,9 +9,7 @@ import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.chat.api.pojo.response.EntityInfo; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.api.pojo.response.QueryState; -import com.tencent.supersonic.chat.config.ChatConfigRich; import com.tencent.supersonic.chat.query.QueryManager; -import com.tencent.supersonic.chat.service.ConfigService; import com.tencent.supersonic.chat.service.SemanticService; import com.tencent.supersonic.chat.utils.ComponentFactory; import com.tencent.supersonic.chat.utils.QueryReqBuilder; @@ -24,6 +22,7 @@ import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; import java.io.Serializable; import java.util.*; @@ -42,33 +41,51 @@ public abstract class RuleSemanticQuery implements SemanticQuery, Serializable { public List match(List candidateElementMatches, QueryContext queryCtx) { - return queryMatcher.match(candidateElementMatches, queryCtx.getRequest().getQueryFilters()); + return queryMatcher.match(candidateElementMatches); } - public void fillParseInfo(Long domainId, ChatContext chatContext){ + public void fillParseInfo(Long domainId, ChatContext chatContext) { parseInfo.setQueryMode(getQueryMode()); - ConfigService configService = ContextUtils.getBean(ConfigService.class); - ChatConfigRich chatConfig = configService.getConfigRichInfo(domainId); - SemanticService schemaService = ContextUtils.getBean(SemanticService.class); DomainSchema domainSchema = schemaService.getDomainSchema(domainId); - fillSchemaElement(parseInfo, domainSchema, chatConfig); + fillSchemaElement(parseInfo, domainSchema); // inherit date info from context - if (parseInfo.getDateInfo() == null && chatContext.getParseInfo().getDateInfo() != null) { + if (parseInfo.getDateInfo() == null && chatContext.getParseInfo().getDateInfo() != null + && isSameQueryMode(getQueryMode(), chatContext.getParseInfo().getQueryMode())) { + log.info("inherit date info from context"); parseInfo.setDateInfo(chatContext.getParseInfo().getDateInfo()); } } - private void fillSchemaElement(SemanticParseInfo parseInfo, DomainSchema domainSchema, ChatConfigRich chaConfigRich) { + public boolean isSameQueryMode(String queryModeQuery, String queryModeChat) { + if (Strings.isNotEmpty(queryModeQuery) && Strings.isNotEmpty(queryModeChat)) { + return QueryManager.isEntityQuery(queryModeQuery) && QueryManager.isEntityQuery(queryModeChat) + || QueryManager.isMetricQuery(queryModeQuery) && QueryManager.isMetricQuery(queryModeChat); + } + return true; + } + + private void fillSchemaElement(SemanticParseInfo parseInfo, DomainSchema domainSchema) { parseInfo.setDomain(domainSchema.getDomain()); Map> dim2Values = new HashMap<>(); + Map> id2Values = new HashMap<>(); + for (SchemaElementMatch schemaMatch : parseInfo.getElementMatches()) { SchemaElement element = schemaMatch.getElement(); switch (element.getType()) { case ID: + SchemaElement entityElement = domainSchema.getElement(SchemaElementType.ENTITY, element.getId()); + if (entityElement != null) { + if (id2Values.containsKey(element.getId())) { + id2Values.get(element.getId()).add(schemaMatch); + } else { + id2Values.put(element.getId(), new ArrayList<>(Arrays.asList(schemaMatch))); + } + } + break; case VALUE: SchemaElement dimElement = domainSchema.getElement(SchemaElementType.DIMENSION, element.getId()); if (dimElement != null) { @@ -85,10 +102,41 @@ public abstract class RuleSemanticQuery implements SemanticQuery, Serializable { case METRIC: parseInfo.getMetrics().add(element); break; + case ENTITY: + parseInfo.setEntity(element); + break; default: } } + if (!id2Values.isEmpty()) { + for (Map.Entry> entry : id2Values.entrySet()) { + SchemaElement entity = domainSchema.getElement(SchemaElementType.ENTITY, entry.getKey()); + + if (entry.getValue().size() == 1) { + SchemaElementMatch schemaMatch = entry.getValue().get(0); + QueryFilter dimensionFilter = new QueryFilter(); + dimensionFilter.setValue(schemaMatch.getWord()); + dimensionFilter.setBizName(entity.getBizName()); + dimensionFilter.setName(entity.getName()); + dimensionFilter.setOperator(FilterOperatorEnum.EQUALS); + dimensionFilter.setElementID(schemaMatch.getElement().getId()); + parseInfo.getDimensionFilters().add(dimensionFilter); + parseInfo.setEntity(domainSchema.getEntity()); + } else { + QueryFilter dimensionFilter = new QueryFilter(); + List vals = new ArrayList<>(); + entry.getValue().stream().forEach(i -> vals.add(i.getWord())); + dimensionFilter.setValue(vals); + dimensionFilter.setBizName(entity.getBizName()); + dimensionFilter.setName(entity.getName()); + dimensionFilter.setOperator(FilterOperatorEnum.IN); + dimensionFilter.setElementID(entry.getKey()); + parseInfo.getDimensionFilters().add(dimensionFilter); + } + } + } + if (!dim2Values.isEmpty()) { for (Map.Entry> entry : dim2Values.entrySet()) { SchemaElement dimension = domainSchema.getElement(SchemaElementType.DIMENSION, entry.getKey()); @@ -102,7 +150,7 @@ public abstract class RuleSemanticQuery implements SemanticQuery, Serializable { dimensionFilter.setOperator(FilterOperatorEnum.EQUALS); dimensionFilter.setElementID(schemaMatch.getElement().getId()); parseInfo.getDimensionFilters().add(dimensionFilter); - setEntityId(schemaMatch.getWord(), chaConfigRich, parseInfo); + parseInfo.setEntity(domainSchema.getEntity()); } else { QueryFilter dimensionFilter = new QueryFilter(); List vals = new ArrayList<>(); @@ -118,16 +166,6 @@ public abstract class RuleSemanticQuery implements SemanticQuery, Serializable { } } - public void setEntityId(String value, ChatConfigRich chaConfigRichDesc, - SemanticParseInfo semanticParseInfo) { - if (chaConfigRichDesc != null && chaConfigRichDesc.getChatDetailRichConfig() != null - && chaConfigRichDesc.getChatDetailRichConfig().getEntity() != null) { - SchemaElement dimSchemaResp = chaConfigRichDesc.getChatDetailRichConfig().getEntity().getDimItem(); - if (Objects.nonNull(dimSchemaResp) && StringUtils.isNumeric(value)) { - semanticParseInfo.setEntity(Long.valueOf(value)); - } - } - } @Override public QueryResult execute(User user) { @@ -202,12 +240,13 @@ public abstract class RuleSemanticQuery implements SemanticQuery, Serializable { return parseInfo; } + @Override public void setParseInfo(SemanticParseInfo parseInfo) { this.parseInfo = parseInfo; } public static List resolve(List candidateElementMatches, - QueryContext queryContext) { + QueryContext queryContext) { List matchedQueries = new ArrayList<>(); for (RuleSemanticQuery semanticQuery : QueryManager.getRuleQueries()) { List matches = semanticQuery.match(candidateElementMatches, queryContext); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityDetailQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityDetailQuery.java index 6ca497420..e9c4b699d 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityDetailQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityDetailQuery.java @@ -14,7 +14,7 @@ public class EntityDetailQuery extends EntitySemanticQuery { public EntityDetailQuery() { super(); queryMatcher.addOption(DIMENSION, REQUIRED, AT_LEAST, 1) - .addOption(VALUE, REQUIRED, AT_LEAST, 1); + .addOption(ID, REQUIRED, AT_LEAST, 1); } @Override diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityFilterQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityFilterQuery.java index 5899d9ccb..a7866eac6 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityFilterQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityFilterQuery.java @@ -1,8 +1,8 @@ package com.tencent.supersonic.chat.query.rule.entity; import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.OPTIONAL; import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.*; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.REQUIRED; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -16,7 +16,8 @@ public class EntityFilterQuery extends EntityListQuery { public EntityFilterQuery() { super(); - queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 1); + queryMatcher.addOption(VALUE, OPTIONAL, AT_LEAST, 0); + queryMatcher.addOption(ID, OPTIONAL, AT_LEAST, 0); } @Override diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityListQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityListQuery.java index 45398e1aa..c5ddcee52 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityListQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityListQuery.java @@ -1,37 +1,43 @@ package com.tencent.supersonic.chat.query.rule.entity; import com.tencent.supersonic.chat.api.pojo.ChatContext; +import com.tencent.supersonic.chat.api.pojo.DomainSchema; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.config.ChatConfigRich; -import com.tencent.supersonic.chat.config.ChatDefaultRichConfig; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatDefaultRichConfigResp; import com.tencent.supersonic.chat.service.ConfigService; +import com.tencent.supersonic.chat.service.SemanticService; import com.tencent.supersonic.common.pojo.Constants; import com.tencent.supersonic.common.pojo.Order; import com.tencent.supersonic.common.util.ContextUtils; import java.util.LinkedHashSet; +import java.util.Objects; import java.util.Set; -public abstract class EntityListQuery extends EntitySemanticQuery{ +public abstract class EntityListQuery extends EntitySemanticQuery { @Override - public void fillParseInfo(Long domainId, ChatContext chatContext){ + public void fillParseInfo(Long domainId, ChatContext chatContext) { super.fillParseInfo(domainId, chatContext); this.addEntityDetailAndOrderByMetric(parseInfo); } private void addEntityDetailAndOrderByMetric(SemanticParseInfo parseInfo) { - if (parseInfo.getDomainId() > 0L) { + Long domainId = parseInfo.getDomainId(); + if (Objects.nonNull(domainId) && domainId > 0L) { ConfigService configService = ContextUtils.getBean(ConfigService.class); - ChatConfigRich chaConfigRichDesc = configService.getConfigRichInfo( - parseInfo.getDomainId()); + ChatConfigRichResp chaConfigRichDesc = configService.getConfigRichInfo(parseInfo.getDomainId()); + SemanticService schemaService = ContextUtils.getBean(SemanticService.class); + DomainSchema domainSchema = schemaService.getDomainSchema(domainId); + if (chaConfigRichDesc != null && chaConfigRichDesc.getChatDetailRichConfig() != null - && chaConfigRichDesc.getChatDetailRichConfig().getEntity() != null) { + && Objects.nonNull(domainSchema) && Objects.nonNull(domainSchema.getEntity())) { Set dimensions = new LinkedHashSet(); Set metrics = new LinkedHashSet(); Set orders = new LinkedHashSet(); - ChatDefaultRichConfig chatDefaultConfig = chaConfigRichDesc.getChatDetailRichConfig().getChatDefaultConfig(); + ChatDefaultRichConfigResp chatDefaultConfig = chaConfigRichDesc.getChatDetailRichConfig().getChatDefaultConfig(); if (chatDefaultConfig != null) { chatDefaultConfig.getMetrics().stream() .forEach(metric -> { diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntitySemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntitySemanticQuery.java index 50981348e..d5813aa6b 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntitySemanticQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntitySemanticQuery.java @@ -4,9 +4,9 @@ import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.QueryContext; import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; import com.tencent.supersonic.chat.api.pojo.SchemaElementType; -import com.tencent.supersonic.chat.config.ChatConfigResp; -import com.tencent.supersonic.chat.config.ChatConfigRich; -import com.tencent.supersonic.chat.config.ChatDefaultRichConfig; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatDefaultRichConfigResp; import com.tencent.supersonic.chat.query.rule.RuleSemanticQuery; import com.tencent.supersonic.chat.service.ConfigService; import com.tencent.supersonic.common.pojo.DateConf; @@ -85,8 +85,8 @@ public abstract class EntitySemanticQuery extends RuleSemanticQuery { parseInfo.setLimit(ENTITY_MAX_RESULTS); if (parseInfo.getDateInfo() == null) { ConfigService configService = ContextUtils.getBean(ConfigService.class); - ChatConfigRich chatConfig = configService.getConfigRichInfo(parseInfo.getDomainId()); - ChatDefaultRichConfig defaultConfig = chatConfig.getChatDetailRichConfig().getChatDefaultConfig(); + ChatConfigRichResp chatConfig = configService.getConfigRichInfo(parseInfo.getDomainId()); + ChatDefaultRichConfigResp defaultConfig = chatConfig.getChatDetailRichConfig().getChatDefaultConfig(); int unit = 1; if (Objects.nonNull(defaultConfig) && Objects.nonNull(defaultConfig.getUnit())) { @@ -94,7 +94,7 @@ public abstract class EntitySemanticQuery extends RuleSemanticQuery { } String date = LocalDate.now().plusDays(-unit).toString(); DateConf dateInfo = new DateConf(); - dateInfo.setDateMode(DateConf.DateMode.BETWEEN_CONTINUOUS); + dateInfo.setDateMode(DateConf.DateMode.BETWEEN); dateInfo.setStartDate(date); dateInfo.setEndDate(date); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityTopNQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityTopNQuery.java deleted file mode 100644 index ae0d34e61..000000000 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/entity/EntityTopNQuery.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.tencent.supersonic.chat.query.rule.entity; - -import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.AT_LEAST; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.REQUIRED; - -import org.springframework.stereotype.Component; - -@Component -public class EntityTopNQuery extends EntityListQuery { - - public static final String QUERY_MODE = "ENTITY_LIST_TOPN"; - - public EntityTopNQuery() { - super(); - queryMatcher.addOption(METRIC, REQUIRED, AT_LEAST, 1) - .setSupportOrderBy(true); - } - - @Override - public String getQueryMode() { - return QUERY_MODE; - } - -} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricDomainQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricDomainQuery.java index badc4aa2b..4cf92ba8f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricDomainQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricDomainQuery.java @@ -1,17 +1,12 @@ package com.tencent.supersonic.chat.query.rule.metric; -import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.api.pojo.response.AggregateInfo; -import com.tencent.supersonic.chat.api.pojo.response.QueryResult; -import com.tencent.supersonic.chat.service.SemanticService; -import com.tencent.supersonic.common.util.ContextUtils; -import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp; -import java.util.Objects; -import org.springframework.stereotype.Component; - -import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.DOMAIN; import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.OPTIONAL; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.*; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.AT_MOST; + +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.pojo.response.QueryResult; +import org.springframework.stereotype.Component; @Component public class MetricDomainQuery extends MetricSemanticQuery { @@ -31,14 +26,7 @@ public class MetricDomainQuery extends MetricSemanticQuery { @Override public QueryResult execute(User user) { QueryResult queryResult = super.execute(user); - if (!Objects.isNull(queryResult)) { - QueryResultWithSchemaResp queryResp = new QueryResultWithSchemaResp(); - queryResp.setColumns(queryResult.getQueryColumns()); - queryResp.setResultList(queryResult.getQueryResults()); - AggregateInfo aggregateInfo = ContextUtils.getBean(SemanticService.class) - .getAggregateInfo(user, parseInfo, queryResp); - queryResult.setAggregateInfo(aggregateInfo); - } + fillAggregateInfo(user, queryResult); return queryResult; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricEntityQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricEntityQuery.java new file mode 100644 index 000000000..3cd998ef4 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricEntityQuery.java @@ -0,0 +1,92 @@ +package com.tencent.supersonic.chat.query.rule.metric; + +import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.chat.api.pojo.response.QueryResult; +import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; +import com.tencent.supersonic.semantic.api.query.pojo.Filter; +import com.tencent.supersonic.semantic.api.query.request.QueryMultiStructReq; +import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.OPTIONAL; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.REQUIRED; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.AT_LEAST; + +@Slf4j +@Component +public class MetricEntityQuery extends MetricSemanticQuery { + + public static final String QUERY_MODE = "METRIC_ENTITY"; + + public MetricEntityQuery() { + super(); + queryMatcher.addOption(ID, REQUIRED, AT_LEAST, 1) + .addOption(ENTITY, REQUIRED, AT_LEAST, 1); + } + + @Override + public String getQueryMode() { + return QUERY_MODE; + } + + @Override + public QueryResult execute(User user) { + if (!isMultiStructQuery()) { + QueryResult queryResult = super.execute(user); + fillAggregateInfo(user, queryResult); + return queryResult; + } + return super.multiStructExecute(user); + } + + protected boolean isMultiStructQuery() { + Set filterBizName = new HashSet<>(); + parseInfo.getDimensionFilters().stream() + .filter(filter -> filter.getElementID() != null) + .forEach(filter -> filterBizName.add(filter.getBizName())); + return filterBizName.size() > 1; + } + + @Override + protected QueryStructReq convertQueryStruct() { + QueryStructReq queryStructReq = super.convertQueryStruct(); + addDimension(queryStructReq, true); + return queryStructReq; + } + + @Override + protected QueryMultiStructReq convertQueryMultiStruct() { + QueryMultiStructReq queryMultiStructReq = super.convertQueryMultiStruct(); + for (QueryStructReq queryStructReq : queryMultiStructReq.getQueryStructReqs()) { + addDimension(queryStructReq, false); + } + return queryMultiStructReq; + } + + private void addDimension(QueryStructReq queryStructReq, boolean onlyOperateInFilter) { + if (!queryStructReq.getDimensionFilters().isEmpty()) { + List dimensions = queryStructReq.getGroups(); + log.info("addDimension before [{}]", queryStructReq.getGroups()); + List filters = new ArrayList<>(queryStructReq.getDimensionFilters()); + if (onlyOperateInFilter) { + filters = filters.stream().filter(filter + -> filter.getOperator().equals(FilterOperatorEnum.IN)).collect(Collectors.toList()); + } + filters.forEach(d -> { + if (!dimensions.contains(d.getBizName())) { + dimensions.add(d.getBizName()); + }}); + queryStructReq.setGroups(dimensions); + log.info("addDimension after [{}]", queryStructReq.getGroups()); + } + } + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricFilterQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricFilterQuery.java index bb6125f47..213397c3e 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricFilterQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricFilterQuery.java @@ -1,23 +1,22 @@ package com.tencent.supersonic.chat.query.rule.metric; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.VALUE; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.REQUIRED; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.AT_LEAST; + import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.api.pojo.response.AggregateInfo; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; -import com.tencent.supersonic.chat.service.SemanticService; -import com.tencent.supersonic.common.util.ContextUtils; -import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp; import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; import com.tencent.supersonic.semantic.api.query.pojo.Filter; import com.tencent.supersonic.semantic.api.query.request.QueryMultiStructReq; import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import java.util.*; -import java.util.stream.Collectors; -import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.*; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.REQUIRED; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.OPTIONAL; @Slf4j @Component @@ -27,8 +26,7 @@ public class MetricFilterQuery extends MetricSemanticQuery { public MetricFilterQuery() { super(); - queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 1) - .addOption(ENTITY, OPTIONAL, AT_MOST, 1); + queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 1); } @Override @@ -40,14 +38,7 @@ public class MetricFilterQuery extends MetricSemanticQuery { public QueryResult execute(User user) { if (!isMultiStructQuery()) { QueryResult queryResult = super.execute(user); - if (Objects.nonNull(queryResult)) { - QueryResultWithSchemaResp queryResp = new QueryResultWithSchemaResp(); - queryResp.setColumns(queryResult.getQueryColumns()); - queryResp.setResultList(queryResult.getQueryResults()); - AggregateInfo aggregateInfo = ContextUtils.getBean(SemanticService.class) - .getAggregateInfo(user,parseInfo,queryResp); - queryResult.setAggregateInfo(aggregateInfo); - } + fillAggregateInfo(user, queryResult); return queryResult; } return super.multiStructExecute(user); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricSemanticQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricSemanticQuery.java index 177123be8..dfcd8898d 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricSemanticQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricSemanticQuery.java @@ -1,27 +1,32 @@ package com.tencent.supersonic.chat.query.rule.metric; +import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.METRIC; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.REQUIRED; +import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.AT_LEAST; + +import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.QueryContext; import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch; import com.tencent.supersonic.chat.api.pojo.SchemaElementType; -import com.tencent.supersonic.chat.config.ChatConfigResp; -import com.tencent.supersonic.chat.config.ChatConfigRich; -import com.tencent.supersonic.chat.config.ChatDefaultRichConfig; +import com.tencent.supersonic.chat.api.pojo.request.ChatDefaultConfigReq; +import com.tencent.supersonic.chat.api.pojo.response.AggregateInfo; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatDefaultRichConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.query.rule.RuleSemanticQuery; import com.tencent.supersonic.chat.service.ConfigService; +import com.tencent.supersonic.chat.service.SemanticService; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.common.util.ContextUtils; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections.CollectionUtils; - +import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Objects; - -import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.METRIC; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.RequireNumberType.AT_LEAST; -import static com.tencent.supersonic.chat.query.rule.QueryMatchOption.OptionType.REQUIRED; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; @Slf4j public abstract class MetricSemanticQuery extends RuleSemanticQuery { @@ -78,23 +83,30 @@ public abstract class MetricSemanticQuery extends RuleSemanticQuery { } @Override - public void fillParseInfo(Long domainId, ChatContext chatContext){ + public void fillParseInfo(Long domainId, ChatContext chatContext) { super.fillParseInfo(domainId, chatContext); parseInfo.setLimit(METRIC_MAX_RESULTS); if (parseInfo.getDateInfo() == null) { ConfigService configService = ContextUtils.getBean(ConfigService.class); - ChatConfigRich chatConfig = configService.getConfigRichInfo(parseInfo.getDomainId()); - ChatDefaultRichConfig defaultConfig = chatConfig.getChatAggRichConfig().getChatDefaultConfig(); - + ChatConfigRichResp chatConfig = configService.getConfigRichInfo(parseInfo.getDomainId()); + ChatDefaultRichConfigResp defaultConfig = chatConfig.getChatAggRichConfig().getChatDefaultConfig(); + DateConf dateInfo = new DateConf(); int unit = 1; if (Objects.nonNull(defaultConfig) && Objects.nonNull(defaultConfig.getUnit())) { unit = defaultConfig.getUnit(); } String startDate = LocalDate.now().plusDays(-unit).toString(); - String endDate = LocalDate.now().plusDays(-1).toString(); - DateConf dateInfo = new DateConf(); - dateInfo.setDateMode(DateConf.DateMode.BETWEEN_CONTINUOUS); + String endDate = startDate; + + if (ChatDefaultConfigReq.TimeMode.LAST.equals(defaultConfig.getTimeMode())) { + dateInfo.setDateMode(DateConf.DateMode.BETWEEN); + } else if (ChatDefaultConfigReq.TimeMode.RECENT.equals(defaultConfig.getTimeMode())) { + dateInfo.setDateMode(DateConf.DateMode.RECENT); + endDate = LocalDate.now().plusDays(-1).toString(); + } + dateInfo.setUnit(unit); + dateInfo.setPeriod(defaultConfig.getPeriod()); dateInfo.setStartDate(startDate); dateInfo.setEndDate(endDate); @@ -102,4 +114,15 @@ public abstract class MetricSemanticQuery extends RuleSemanticQuery { } } + public void fillAggregateInfo(User user, QueryResult queryResult) { + if (Objects.nonNull(queryResult)) { + QueryResultWithSchemaResp queryResp = new QueryResultWithSchemaResp(); + queryResp.setColumns(queryResult.getQueryColumns()); + queryResp.setResultList(queryResult.getQueryResults()); + AggregateInfo aggregateInfo = ContextUtils.getBean(SemanticService.class) + .getAggregateInfo(user, parseInfo, queryResp); + queryResult.setAggregateInfo(aggregateInfo); + } + } + } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricTopNQuery.java b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricTopNQuery.java index 196e8e70a..01861694b 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricTopNQuery.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/query/rule/metric/MetricTopNQuery.java @@ -54,7 +54,7 @@ public class MetricTopNQuery extends MetricSemanticQuery { super.fillParseInfo(domainId, chatContext); parseInfo.setLimit(ORDERBY_MAX_RESULTS); - parseInfo.setBonus(2.0); + parseInfo.setScore(2.0); parseInfo.setAggType(AggregateTypeEnum.SUM); SchemaElement metric = parseInfo.getMetrics().iterator().next(); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java index d1ece0e2f..029faeb26 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatConfigController.java @@ -4,7 +4,11 @@ import com.github.pagehelper.PageInfo; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.auth.api.authentication.utils.UserHolder; import com.tencent.supersonic.chat.api.component.SemanticLayer; -import com.tencent.supersonic.chat.config.*; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigBaseReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigEditReqReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigFilter; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; import com.tencent.supersonic.chat.utils.ComponentFactory; import com.tencent.supersonic.semantic.api.model.request.PageDimensionReq; import com.tencent.supersonic.semantic.api.model.request.PageMetricReq; @@ -65,12 +69,12 @@ public class ChatConfigController { @GetMapping("/richDesc/{domainId}") - public ChatConfigRich getDomainExtendRichInfo(@PathVariable("domainId") Long domainId) { + public ChatConfigRichResp getDomainExtendRichInfo(@PathVariable("domainId") Long domainId) { return configService.getConfigRichInfo(domainId); } @GetMapping("/richDesc/all") - public List getAllChatRichConfig() { + public List getAllChatRichConfig() { return configService.getAllChatRichConfig(); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java index 85b0ea412..18f5875f5 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatController.java @@ -4,7 +4,7 @@ package com.tencent.supersonic.chat.rest; import com.github.pagehelper.PageInfo; import com.tencent.supersonic.auth.api.authentication.utils.UserHolder; import com.tencent.supersonic.chat.persistence.dataobject.ChatDO; -import com.tencent.supersonic.chat.api.pojo.response.QueryResponse; +import com.tencent.supersonic.chat.api.pojo.response.QueryResp; import com.tencent.supersonic.chat.api.pojo.request.PageQueryInfoReq; import com.tencent.supersonic.chat.service.ChatService; import java.util.List; @@ -68,10 +68,10 @@ public class ChatController { } @PostMapping("/pageQueryInfo") - public PageInfo pageQueryInfo(@RequestBody PageQueryInfoReq pageQueryInfoCommand, - @RequestParam(value = "chatId") long chatId, - HttpServletRequest request, - HttpServletResponse response) { + public PageInfo pageQueryInfo(@RequestBody PageQueryInfoReq pageQueryInfoCommand, + @RequestParam(value = "chatId") long chatId, + HttpServletRequest request, + HttpServletResponse response) { pageQueryInfoCommand.setUserName(UserHolder.findUser(request, response).getName()); return chatService.queryInfo(pageQueryInfoCommand, chatId); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java index 0356e27ef..976f60f1d 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/ChatQueryController.java @@ -2,8 +2,9 @@ package com.tencent.supersonic.chat.rest; import com.tencent.supersonic.auth.api.authentication.utils.UserHolder; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; -import com.tencent.supersonic.chat.api.pojo.request.QueryDataRequest; +import com.tencent.supersonic.chat.api.pojo.request.ExecuteQueryReq; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; +import com.tencent.supersonic.chat.api.pojo.request.QueryDataReq; import com.tencent.supersonic.chat.service.QueryService; import com.tencent.supersonic.chat.service.SearchService; import javax.servlet.http.HttpServletRequest; @@ -31,28 +32,42 @@ public class ChatQueryController { @PostMapping("search") - public Object search(@RequestBody QueryRequest queryCtx, HttpServletRequest request, + public Object search(@RequestBody QueryReq queryCtx, HttpServletRequest request, HttpServletResponse response) { queryCtx.setUser(UserHolder.findUser(request, response)); return searchService.search(queryCtx); } @PostMapping("query") - public Object query(@RequestBody QueryRequest queryCtx, HttpServletRequest request, HttpServletResponse response) + public Object query(@RequestBody QueryReq queryCtx, HttpServletRequest request, HttpServletResponse response) throws Exception { queryCtx.setUser(UserHolder.findUser(request, response)); return queryService.executeQuery(queryCtx); } + @PostMapping("parse") + public Object parse(@RequestBody QueryReq queryCtx, HttpServletRequest request, HttpServletResponse response) + throws Exception { + queryCtx.setUser(UserHolder.findUser(request, response)); + return queryService.performParsing(queryCtx); + } + + @PostMapping("execute") + public Object execute(@RequestBody ExecuteQueryReq queryCtx, HttpServletRequest request, HttpServletResponse response) + throws Exception { + queryCtx.setUser(UserHolder.findUser(request, response)); + return queryService.performExecution(queryCtx); + } + @PostMapping("queryContext") - public Object queryContext(@RequestBody QueryRequest queryCtx, HttpServletRequest request, + public Object queryContext(@RequestBody QueryReq queryCtx, HttpServletRequest request, HttpServletResponse response) throws Exception { queryCtx.setUser(UserHolder.findUser(request, response)); return queryService.queryContext(queryCtx); } @PostMapping("queryData") - public Object queryData(@RequestBody QueryDataRequest queryData, HttpServletRequest request, HttpServletResponse response) + public Object queryData(@RequestBody QueryDataReq queryData, HttpServletRequest request, HttpServletResponse response) throws Exception { return queryService.executeDirectQuery(queryData, UserHolder.findUser(request, response)); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/PluginController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/PluginController.java index c47444f6e..94ee18ade 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/PluginController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/PluginController.java @@ -50,7 +50,7 @@ public class PluginController { } @PostMapping("/query") - List query(PluginQueryReq pluginQueryReq) { + List query(@RequestBody PluginQueryReq pluginQueryReq) { return pluginService.queryWithAuthCheck(pluginQueryReq); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/RecommendController.java b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/RecommendController.java index 7118c20b6..ea58046fb 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/rest/RecommendController.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/rest/RecommendController.java @@ -1,9 +1,9 @@ package com.tencent.supersonic.chat.rest; import com.tencent.supersonic.auth.api.authentication.utils.UserHolder; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; -import com.tencent.supersonic.chat.api.pojo.response.RecommendQuestion; -import com.tencent.supersonic.chat.api.pojo.response.RecommendResponse; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; +import com.tencent.supersonic.chat.api.pojo.response.RecommendQuestionResp; +import com.tencent.supersonic.chat.api.pojo.response.RecommendResp; import com.tencent.supersonic.chat.service.RecommendService; import javax.servlet.http.HttpServletRequest; @@ -25,31 +25,31 @@ public class RecommendController { private RecommendService recommendService; @GetMapping("recommend/{domainId}") - public RecommendResponse recommend(@PathVariable("domainId") Long domainId, - @RequestParam(value = "limit", required = false) Long limit, - HttpServletRequest request, - HttpServletResponse response) { - QueryRequest queryCtx = new QueryRequest(); + public RecommendResp recommend(@PathVariable("domainId") Long domainId, + @RequestParam(value = "limit", required = false) Long limit, + HttpServletRequest request, + HttpServletResponse response) { + QueryReq queryCtx = new QueryReq(); queryCtx.setUser(UserHolder.findUser(request, response)); queryCtx.setDomainId(domainId); return recommendService.recommend(queryCtx, limit); } @GetMapping("recommend/metric/{domainId}") - public RecommendResponse recommendMetricMode(@PathVariable("domainId") Long domainId, - @RequestParam(value = "limit", required = false) Long limit, - HttpServletRequest request, - HttpServletResponse response) { - QueryRequest queryCtx = new QueryRequest(); + public RecommendResp recommendMetricMode(@PathVariable("domainId") Long domainId, + @RequestParam(value = "limit", required = false) Long limit, + HttpServletRequest request, + HttpServletResponse response) { + QueryReq queryCtx = new QueryReq(); queryCtx.setUser(UserHolder.findUser(request, response)); queryCtx.setDomainId(domainId); return recommendService.recommendMetricMode(queryCtx, limit); } @GetMapping("recommend/question") - public List recommendQuestion(@RequestParam(value = "domainId", required = false) Long domainId, - HttpServletRequest request, - HttpServletResponse response) { + public List recommendQuestion(@RequestParam(value = "domainId", required = false) Long domainId, + HttpServletRequest request, + HttpServletResponse response) { return recommendService.recommendQuestion(domainId); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/ChatService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/ChatService.java index 590eec797..d7dc78917 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/ChatService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/ChatService.java @@ -8,7 +8,7 @@ import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.persistence.dataobject.ChatDO; import com.tencent.supersonic.chat.persistence.dataobject.ChatQueryDO; -import com.tencent.supersonic.chat.api.pojo.response.QueryResponse; +import com.tencent.supersonic.chat.api.pojo.response.QueryResp; import com.tencent.supersonic.chat.api.pojo.request.PageQueryInfoReq; import java.util.List; @@ -25,8 +25,6 @@ public interface ChatService { public void updateContext(ChatContext chatCtx); - public void updateContext(ChatContext chatCtx, QueryContext queryCtx, SemanticParseInfo semanticParseInfo); - public void switchContext(ChatContext chatCtx); public Boolean addChat(User user, String chatName); @@ -41,9 +39,9 @@ public interface ChatService { Boolean deleteChat(Long chatId, String userName); - PageInfo queryInfo(PageQueryInfoReq pageQueryInfoCommend, long chatId); + PageInfo queryInfo(PageQueryInfoReq pageQueryInfoCommend, long chatId); - public void addQuery(QueryResult queryResult, QueryContext queryContext, ChatContext chatCtx); + public void addQuery(QueryResult queryResult, ChatContext chatCtx); public ChatQueryDO getLastQuery(long chatId); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/ConfigService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/ConfigService.java index d211d2580..0b1aa8b8f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/ConfigService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/ConfigService.java @@ -2,7 +2,11 @@ package com.tencent.supersonic.chat.service; import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.config.*; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigBaseReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigEditReqReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigFilter; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; import java.util.List; @@ -14,9 +18,9 @@ public interface ConfigService { List search(ChatConfigFilter filter, User user); - ChatConfigRich getConfigRichInfo(Long domainId); + ChatConfigRichResp getConfigRichInfo(Long domainId); ChatConfigResp fetchConfigByDomainId(Long domainId); - List getAllChatRichConfig(); + List getAllChatRichConfig(); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/QueryService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/QueryService.java index b13d5d893..be45d6816 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/QueryService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/QueryService.java @@ -2,9 +2,11 @@ package com.tencent.supersonic.chat.service; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.ExecuteQueryReq; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; +import com.tencent.supersonic.chat.api.pojo.response.ParseResp; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; -import com.tencent.supersonic.chat.api.pojo.request.QueryDataRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryDataReq; import org.apache.calcite.sql.parser.SqlParseException; /*** @@ -12,9 +14,14 @@ import org.apache.calcite.sql.parser.SqlParseException; */ public interface QueryService { - QueryResult executeQuery(QueryRequest queryCtx) throws Exception; + ParseResp performParsing(QueryReq queryReq); - SemanticParseInfo queryContext(QueryRequest queryCtx); + QueryResult performExecution(ExecuteQueryReq queryReq) throws Exception; + + QueryResult executeQuery(QueryReq queryReq) throws Exception; + + SemanticParseInfo queryContext(QueryReq queryReq); + + QueryResult executeDirectQuery(QueryDataReq queryData, User user) throws SqlParseException; - QueryResult executeDirectQuery(QueryDataRequest queryData, User user) throws SqlParseException; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/RecommendService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/RecommendService.java index 8e8aff45c..ce85f7da2 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/RecommendService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/RecommendService.java @@ -1,9 +1,9 @@ package com.tencent.supersonic.chat.service; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; -import com.tencent.supersonic.chat.api.pojo.response.RecommendQuestion; -import com.tencent.supersonic.chat.api.pojo.response.RecommendResponse; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; +import com.tencent.supersonic.chat.api.pojo.response.RecommendQuestionResp; +import com.tencent.supersonic.chat.api.pojo.response.RecommendResp; import java.util.List; @@ -12,9 +12,9 @@ import java.util.List; */ public interface RecommendService { - RecommendResponse recommend(QueryRequest queryCtx, Long limit); + RecommendResp recommend(QueryReq queryCtx, Long limit); - RecommendResponse recommendMetricMode(QueryRequest queryCtx, Long limit); + RecommendResp recommendMetricMode(QueryReq queryCtx, Long limit); - List recommendQuestion(Long domainId); + List recommendQuestion(Long domainId); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/SearchService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/SearchService.java index 2460ef1e6..8f36c0996 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/SearchService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/SearchService.java @@ -1,6 +1,6 @@ package com.tencent.supersonic.chat.service; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.api.pojo.response.SearchResult; import java.util.List; @@ -9,6 +9,6 @@ import java.util.List; */ public interface SearchService { - List search(QueryRequest queryCtx); + List search(QueryReq queryCtx); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/SemanticService.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/SemanticService.java index fdac19b67..d9a43b71f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/SemanticService.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/SemanticService.java @@ -16,19 +16,20 @@ import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.chat.api.pojo.DomainSchema; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.chat.api.pojo.request.ChatAggConfigReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatDefaultConfigReq; +import com.tencent.supersonic.chat.api.pojo.request.ChatDetailConfigReq; +import com.tencent.supersonic.chat.api.pojo.request.ItemVisibility; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.chat.api.pojo.response.AggregateInfo; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatDefaultRichConfigResp; import com.tencent.supersonic.chat.api.pojo.response.DataInfo; import com.tencent.supersonic.chat.api.pojo.response.DomainInfo; import com.tencent.supersonic.chat.api.pojo.response.EntityInfo; import com.tencent.supersonic.chat.api.pojo.response.MetricInfo; -import com.tencent.supersonic.chat.config.ChatAggConfig; -import com.tencent.supersonic.chat.config.ChatConfigResp; -import com.tencent.supersonic.chat.config.ChatConfigRich; -import com.tencent.supersonic.chat.config.ChatDefaultRichConfig; -import com.tencent.supersonic.chat.config.ChatDetailConfig; -import com.tencent.supersonic.chat.config.EntityRichInfo; -import com.tencent.supersonic.chat.config.ItemVisibility; +import com.tencent.supersonic.chat.config.AggregatorConfig; import com.tencent.supersonic.chat.utils.ComponentFactory; import com.tencent.supersonic.chat.utils.QueryReqBuilder; import com.tencent.supersonic.common.pojo.DateConf; @@ -36,11 +37,13 @@ import com.tencent.supersonic.common.pojo.DateConf.DateMode; import com.tencent.supersonic.common.pojo.QueryColumn; import com.tencent.supersonic.common.pojo.enums.AggOperatorEnum; import com.tencent.supersonic.common.pojo.enums.RatioOverType; +import com.tencent.supersonic.common.util.ContextUtils; import com.tencent.supersonic.common.util.DateUtils; import com.tencent.supersonic.knowledge.service.SchemaService; import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp; import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; +import java.text.DecimalFormat; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; @@ -56,6 +59,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -70,6 +74,8 @@ public class SemanticService { private SchemaService schemaService; @Autowired private ConfigService configService; + @Autowired + private AggregatorConfig aggregatorConfig; private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer(); @@ -122,25 +128,30 @@ public class SemanticService { } public EntityInfo getEntityInfo(Long domain) { - ChatConfigRich chaConfigRichDesc = configService.getConfigRichInfo(domain); + ChatConfigRichResp chaConfigRichDesc = configService.getConfigRichInfo(domain); if (Objects.isNull(chaConfigRichDesc) || Objects.isNull(chaConfigRichDesc.getChatDetailRichConfig())) { return new EntityInfo(); } return getEntityInfo(chaConfigRichDesc); } - private EntityInfo getEntityInfo(ChatConfigRich chaConfigRichDesc) { + private EntityInfo getEntityInfo(ChatConfigRichResp chaConfigRichDesc) { EntityInfo entityInfo = new EntityInfo(); - EntityRichInfo entityDesc = chaConfigRichDesc.getChatDetailRichConfig().getEntity(); - if (entityDesc != null && Objects.nonNull(chaConfigRichDesc.getDomainId())) { + Long domainId = chaConfigRichDesc.getDomainId(); + if (Objects.nonNull(chaConfigRichDesc) && Objects.nonNull(domainId)) { + SemanticService schemaService = ContextUtils.getBean(SemanticService.class); + DomainSchema domainSchema = schemaService.getDomainSchema(domainId); + if (Objects.isNull(domainSchema) || Objects.isNull(domainSchema.getEntity())) { + return entityInfo; + } DomainInfo domainInfo = new DomainInfo(); - domainInfo.setItemId(chaConfigRichDesc.getDomainId().intValue()); - domainInfo.setName(chaConfigRichDesc.getDomainName()); - domainInfo.setWords(entityDesc.getNames()); - domainInfo.setBizName(chaConfigRichDesc.getBizName()); - if (Objects.nonNull(entityDesc.getDimItem())) { - domainInfo.setPrimaryEntityBizName(entityDesc.getDimItem().getBizName()); + domainInfo.setItemId(domainId.intValue()); + domainInfo.setName(domainSchema.getDomain().getName()); + domainInfo.setWords(domainSchema.getDomain().getAlias()); + domainInfo.setBizName(domainSchema.getDomain().getBizName()); + if (Objects.nonNull(domainSchema.getEntity())) { + domainInfo.setPrimaryEntityBizName(domainSchema.getEntity().getBizName()); } entityInfo.setDomainInfo(domainInfo); @@ -149,7 +160,7 @@ public class SemanticService { if (Objects.nonNull(chaConfigRichDesc) && Objects.nonNull(chaConfigRichDesc.getChatDetailRichConfig()) && Objects.nonNull(chaConfigRichDesc.getChatDetailRichConfig().getChatDefaultConfig())) { - ChatDefaultRichConfig chatDefaultConfig = chaConfigRichDesc.getChatDetailRichConfig() + ChatDefaultRichConfigResp chatDefaultConfig = chaConfigRichDesc.getChatDetailRichConfig() .getChatDefaultConfig(); if (!CollectionUtils.isEmpty(chatDefaultConfig.getDimensions())) { for (SchemaElement dimensionDesc : chatDefaultConfig.getDimensions()) { @@ -187,8 +198,21 @@ public class SemanticService { semanticParseInfo.setMetrics(getMetrics(domainInfo)); semanticParseInfo.setDimensions(getDimensions(domainInfo)); DateConf dateInfo = new DateConf(); - dateInfo.setUnit(1); - dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + int unit = 1; + ChatConfigResp chatConfigInfo = + configService.fetchConfigByDomainId(domainSchema.getDomain().getId()); + if (Objects.nonNull(chatConfigInfo) && Objects.nonNull(chatConfigInfo.getChatDetailConfig()) + && Objects.nonNull(chatConfigInfo.getChatDetailConfig().getChatDefaultConfig())) { + ChatDefaultConfigReq chatDefaultConfig = chatConfigInfo.getChatDetailConfig().getChatDefaultConfig(); + unit = chatDefaultConfig.getUnit(); + String date = LocalDate.now().plusDays(-unit).toString(); + dateInfo.setDateMode(DateMode.BETWEEN); + dateInfo.setStartDate(date); + dateInfo.setEndDate(date); + } else { + dateInfo.setUnit(unit); + dateInfo.setDateMode(DateMode.RECENT); + } semanticParseInfo.setDateInfo(dateInfo); // add filter @@ -286,8 +310,8 @@ public class SemanticService { private ItemVisibility generateFinalVisibility(ChatConfigResp chatConfigInfo) { ItemVisibility visibility = new ItemVisibility(); - ChatAggConfig chatAggConfig = chatConfigInfo.getChatAggConfig(); - ChatDetailConfig chatDetailConfig = chatConfigInfo.getChatDetailConfig(); + ChatAggConfigReq chatAggConfig = chatConfigInfo.getChatAggConfig(); + ChatDetailConfigReq chatDetailConfig = chatConfigInfo.getChatDetailConfig(); // both black is exist if (Objects.nonNull(chatAggConfig) && Objects.nonNull(chatAggConfig.getVisibility()) @@ -307,8 +331,8 @@ public class SemanticService { } public AggregateInfo getAggregateInfo(User user, SemanticParseInfo semanticParseInfo, - QueryResultWithSchemaResp result) { - if (CollectionUtils.isEmpty(semanticParseInfo.getMetrics())) { + QueryResultWithSchemaResp result) { + if (CollectionUtils.isEmpty(semanticParseInfo.getMetrics()) || !aggregatorConfig.getEnableRatio()) { return new AggregateInfo(); } List resultMetricNames = result.getColumns().stream().map(c -> c.getNameEn()) @@ -319,24 +343,36 @@ public class SemanticService { AggregateInfo aggregateInfo = new AggregateInfo(); MetricInfo metricInfo = new MetricInfo(); metricInfo.setStatistics(new HashMap<>()); - String dateField = QueryReqBuilder.getDateField(semanticParseInfo.getDateInfo()); - - Optional lastDayOp = result.getResultList().stream() - .map(r -> r.get(dateField).toString()) - .sorted(Comparator.reverseOrder()).findFirst(); - if (lastDayOp.isPresent()) { - Optional> lastValue = result.getResultList().stream() - .filter(r -> r.get(dateField).toString().equals(lastDayOp.get())).findFirst(); - if (lastValue.isPresent()) { - metricInfo.setValue(lastValue.get().get(ratioMetric.get().getBizName()).toString()); - } - metricInfo.setDate(lastValue.get().get(dateField).toString()); - } try { - queryRatio(user, semanticParseInfo, ratioMetric.get(), AggOperatorEnum.RATIO_ROLL, - result, metricInfo); - queryRatio(user, semanticParseInfo, ratioMetric.get(), AggOperatorEnum.RATIO_OVER, - result, metricInfo); + String dateField = QueryReqBuilder.getDateField(semanticParseInfo.getDateInfo()); + + Optional lastDayOp = result.getResultList().stream().filter(r -> r.containsKey(dateField)) + .map(r -> r.get(dateField).toString()) + .sorted(Comparator.reverseOrder()).findFirst(); + if (lastDayOp.isPresent()) { + Optional> lastValue = result.getResultList().stream() + .filter(r -> r.get(dateField).toString().equals(lastDayOp.get())).findFirst(); + if (lastValue.isPresent() && lastValue.get().containsKey(ratioMetric.get().getBizName())) { + DecimalFormat df = new DecimalFormat("#.####"); + metricInfo.setValue(df.format(lastValue.get().get(ratioMetric.get().getBizName()))); + } + metricInfo.setDate(lastValue.get().get(dateField).toString()); + } + CompletableFuture metricInfoRoll = CompletableFuture + .supplyAsync(() -> { + return queryRatio(user, semanticParseInfo, ratioMetric.get(), AggOperatorEnum.RATIO_ROLL, + result); + }); + CompletableFuture metricInfoOver = CompletableFuture + .supplyAsync(() -> { + return queryRatio(user, semanticParseInfo, ratioMetric.get(), AggOperatorEnum.RATIO_OVER, + result); + }); + CompletableFuture.allOf(metricInfoRoll, metricInfoOver); + metricInfo.setName(metricInfoRoll.get().getName()); + metricInfo.setValue(metricInfoRoll.get().getValue()); + metricInfo.getStatistics().putAll(metricInfoRoll.get().getStatistics()); + metricInfo.getStatistics().putAll(metricInfoOver.get().getStatistics()); aggregateInfo.getMetricInfos().add(metricInfo); } catch (Exception e) { log.error("queryRatio error {}", e); @@ -346,8 +382,10 @@ public class SemanticService { return new AggregateInfo(); } - private void queryRatio(User user, SemanticParseInfo semanticParseInfo, SchemaElement metric, - AggOperatorEnum aggOperatorEnum, QueryResultWithSchemaResp results, MetricInfo metricInfo) { + private MetricInfo queryRatio(User user, SemanticParseInfo semanticParseInfo, SchemaElement metric, + AggOperatorEnum aggOperatorEnum, QueryResultWithSchemaResp results) { + MetricInfo metricInfo = new MetricInfo(); + metricInfo.setStatistics(new HashMap<>()); QueryStructReq queryStructReq = QueryReqBuilder.buildStructRatioReq(semanticParseInfo, metric, aggOperatorEnum); DateConf dateInfo = semanticParseInfo.getDateInfo(); String dateField = QueryReqBuilder.getDateField(dateInfo); @@ -360,12 +398,18 @@ public class SemanticService { Map result = queryResp.getResultList().get(0); Optional valueColumn = queryResp.getColumns().stream() .filter(c -> c.getNameEn().equals(metric.getBizName())).findFirst(); - if (valueColumn.isPresent()) { + + String valueField = String.format("%s_%s", valueColumn.get().getNameEn(), + aggOperatorEnum.getOperator()); + if (result.containsKey(valueColumn.get().getNameEn())) { + DecimalFormat df = new DecimalFormat("#.####"); + metricInfo.setValue(df.format(result.get(valueColumn.get().getNameEn()))); + } String ratio = ""; - if (Objects.nonNull(result.get(valueColumn.get().getNameEn()))) { + if (Objects.nonNull(result.get(valueField))) { ratio = String.format("%.2f", - (Double.valueOf(result.get(valueColumn.get().getNameEn()).toString()) * 100)) + "%"; + (Double.valueOf(result.get(valueField).toString()) * 100)) + "%"; } String statisticsRollName = RatioOverType.DAY_ON_DAY.getShowName(); String statisticsOverName = RatioOverType.WEEK_ON_DAY.getShowName(); @@ -383,10 +427,11 @@ public class SemanticService { } metricInfo.setName(metric.getName()); } + return metricInfo; } private DateConf getRatioDateConf(AggOperatorEnum aggOperatorEnum, SemanticParseInfo semanticParseInfo, - QueryResultWithSchemaResp results) { + QueryResultWithSchemaResp results) { String dateField = QueryReqBuilder.getDateField(semanticParseInfo.getDateInfo()); Optional lastDayOp = results.getResultList().stream() .map(r -> r.get(dateField).toString()) @@ -395,7 +440,7 @@ public class SemanticService { String lastDay = lastDayOp.get(); DateConf dateConf = new DateConf(); dateConf.setPeriod(semanticParseInfo.getDateInfo().getPeriod()); - dateConf.setDateMode(DateMode.LIST_DISCRETE); + dateConf.setDateMode(DateMode.LIST); List dayList = new ArrayList<>(); dayList.add(lastDay); String start = ""; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ChatServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ChatServiceImpl.java index 9a61e618c..21077ab10 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ChatServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ChatServiceImpl.java @@ -9,7 +9,7 @@ import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.persistence.dataobject.ChatDO; import com.tencent.supersonic.chat.persistence.dataobject.ChatQueryDO; import com.tencent.supersonic.chat.persistence.dataobject.QueryDO; -import com.tencent.supersonic.chat.api.pojo.response.QueryResponse; +import com.tencent.supersonic.chat.api.pojo.response.QueryResp; import com.tencent.supersonic.chat.api.pojo.request.PageQueryInfoReq; import com.tencent.supersonic.chat.persistence.repository.ChatContextRepository; import com.tencent.supersonic.chat.persistence.repository.ChatQueryRepository; @@ -68,14 +68,6 @@ public class ChatServiceImpl implements ChatService { chatContextRepository.updateContext(chatCtx); } - @Override - public void updateContext(ChatContext chatCtx, QueryContext queryCtx, - SemanticParseInfo semanticParseInfo) { - chatCtx.setParseInfo(semanticParseInfo); - chatCtx.setQueryText(queryCtx.getRequest().getQueryText()); - updateContext(chatCtx); - } - @Override public void switchContext(ChatContext chatCtx) { log.debug("switchContext ChatContext {}", chatCtx); @@ -126,15 +118,15 @@ public class ChatServiceImpl implements ChatService { } @Override - public PageInfo queryInfo(PageQueryInfoReq pageQueryInfoCommend, long chatId) { + public PageInfo queryInfo(PageQueryInfoReq pageQueryInfoCommend, long chatId) { return chatQueryRepository.getChatQuery(pageQueryInfoCommend, chatId); } @Override - public void addQuery(QueryResult queryResult, QueryContext queryContext, ChatContext chatCtx) { - chatQueryRepository.createChatQuery(queryResult, queryContext.getRequest(), chatCtx); + public void addQuery(QueryResult queryResult, ChatContext chatCtx) { + chatQueryRepository.createChatQuery(queryResult, chatCtx); chatRepository.updateLastQuestion(chatCtx.getChatId().longValue(), - queryContext.getRequest().getQueryText(), getCurrentTime()); + chatCtx.getQueryText(), getCurrentTime()); } @Override diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ConfigServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ConfigServiceImpl.java index 3c1a762f3..3e652b64c 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ConfigServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/ConfigServiceImpl.java @@ -5,6 +5,8 @@ import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.chat.api.pojo.DomainSchema; import com.tencent.supersonic.chat.api.pojo.SchemaElement; +import com.tencent.supersonic.chat.api.pojo.request.*; +import com.tencent.supersonic.chat.api.pojo.response.*; import com.tencent.supersonic.chat.config.*; import com.tencent.supersonic.chat.service.ConfigService; import com.tencent.supersonic.chat.service.SemanticService; @@ -135,8 +137,8 @@ public class ConfigServiceImpl implements ConfigService { } @Override - public ChatConfigRich getConfigRichInfo(Long domainId) { - ChatConfigRich chatConfigRich = new ChatConfigRich(); + public ChatConfigRichResp getConfigRichInfo(Long domainId) { + ChatConfigRichResp chatConfigRich = new ChatConfigRichResp(); ChatConfigResp chatConfigResp = chatConfigRepository.getConfigByDomainId(domainId); if (Objects.isNull(chatConfigResp)) { log.info("there is no chatConfigDesc for domainId:{}", domainId); @@ -154,24 +156,23 @@ public class ConfigServiceImpl implements ConfigService { return chatConfigRich; } - private ChatDetailRichConfig fillChatDetailRichConfig(DomainSchema domainSchema, ChatConfigRich chatConfigRich, ChatConfigResp chatConfigResp) { + private ChatDetailRichConfigResp fillChatDetailRichConfig(DomainSchema domainSchema, ChatConfigRichResp chatConfigRich, ChatConfigResp chatConfigResp) { if (Objects.isNull(chatConfigResp) || Objects.isNull(chatConfigResp.getChatDetailConfig())) { return null; } - ChatDetailRichConfig detailRichConfig = new ChatDetailRichConfig(); - ChatDetailConfig chatDetailConfig = chatConfigResp.getChatDetailConfig(); + ChatDetailRichConfigResp detailRichConfig = new ChatDetailRichConfigResp(); + ChatDetailConfigReq chatDetailConfig = chatConfigResp.getChatDetailConfig(); ItemVisibilityInfo itemVisibilityInfo = fetchVisibilityDescByConfig(chatDetailConfig.getVisibility(), domainSchema); detailRichConfig.setVisibility(itemVisibilityInfo); detailRichConfig.setKnowledgeInfos(fillKnowledgeBizName(chatDetailConfig.getKnowledgeInfos(), domainSchema)); detailRichConfig.setGlobalKnowledgeConfig(chatDetailConfig.getGlobalKnowledgeConfig()); detailRichConfig.setChatDefaultConfig(fetchDefaultConfig(chatDetailConfig.getChatDefaultConfig(), domainSchema, itemVisibilityInfo)); - detailRichConfig.setEntity(generateRichEntity(chatDetailConfig.getEntity(), domainSchema)); return detailRichConfig; } - private EntityRichInfo generateRichEntity(Entity entity, DomainSchema domainSchema) { - EntityRichInfo entityRichInfo = new EntityRichInfo(); + private EntityRichInfoResp generateRichEntity(Entity entity, DomainSchema domainSchema) { + EntityRichInfoResp entityRichInfo = new EntityRichInfoResp(); if (Objects.isNull(entity) || Objects.isNull(entity.getEntityId())) { return entityRichInfo; } @@ -183,12 +184,12 @@ public class ConfigServiceImpl implements ConfigService { return entityRichInfo; } - private ChatAggRichConfig fillChatAggRichConfig(DomainSchema domainSchema, ChatConfigResp chatConfigResp) { + private ChatAggRichConfigResp fillChatAggRichConfig(DomainSchema domainSchema, ChatConfigResp chatConfigResp) { if (Objects.isNull(chatConfigResp) || Objects.isNull(chatConfigResp.getChatAggConfig())) { return null; } - ChatAggConfig chatAggConfig = chatConfigResp.getChatAggConfig(); - ChatAggRichConfig chatAggRichConfig = new ChatAggRichConfig(); + ChatAggConfigReq chatAggConfig = chatConfigResp.getChatAggConfig(); + ChatAggRichConfigResp chatAggRichConfig = new ChatAggRichConfigResp(); ItemVisibilityInfo itemVisibilityInfo = fetchVisibilityDescByConfig(chatAggConfig.getVisibility(), domainSchema); chatAggRichConfig.setVisibility(itemVisibilityInfo); chatAggRichConfig.setKnowledgeInfos(fillKnowledgeBizName(chatAggConfig.getKnowledgeInfos(), domainSchema)); @@ -198,8 +199,8 @@ public class ConfigServiceImpl implements ConfigService { return chatAggRichConfig; } - private ChatDefaultRichConfig fetchDefaultConfig(ChatDefaultConfig chatDefaultConfig, DomainSchema domainSchema, ItemVisibilityInfo itemVisibilityInfo) { - ChatDefaultRichConfig defaultRichConfig = new ChatDefaultRichConfig(); + private ChatDefaultRichConfigResp fetchDefaultConfig(ChatDefaultConfigReq chatDefaultConfig, DomainSchema domainSchema, ItemVisibilityInfo itemVisibilityInfo) { + ChatDefaultRichConfigResp defaultRichConfig = new ChatDefaultRichConfigResp(); if (Objects.isNull(chatDefaultConfig)) { return defaultRichConfig; } @@ -245,8 +246,8 @@ public class ConfigServiceImpl implements ConfigService { } - private List fillKnowledgeBizName(List knowledgeInfos, - DomainSchema domainSchema) { + private List fillKnowledgeBizName(List knowledgeInfos, + DomainSchema domainSchema) { if (CollectionUtils.isEmpty(knowledgeInfos)) { return new ArrayList<>(); } @@ -264,11 +265,11 @@ public class ConfigServiceImpl implements ConfigService { } @Override - public List getAllChatRichConfig() { - List chatConfigRichInfoList = new ArrayList<>(); + public List getAllChatRichConfig() { + List chatConfigRichInfoList = new ArrayList<>(); List domainRespList = semanticLayer.getDomainListForAdmin(); domainRespList.stream().forEach(domainResp -> { - ChatConfigRich chatConfigRichInfo = getConfigRichInfo(domainResp.getId()); + ChatConfigRichResp chatConfigRichInfo = getConfigRichInfo(domainResp.getId()); if (Objects.nonNull(chatConfigRichInfo)) { chatConfigRichInfoList.add(chatConfigRichInfo); } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/PluginServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/PluginServiceImpl.java index d8f87a51e..ae2223165 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/PluginServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/PluginServiceImpl.java @@ -3,6 +3,7 @@ package com.tencent.supersonic.chat.service.impl; import com.google.common.collect.Lists; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.plugin.PluginParseConfig; import com.tencent.supersonic.chat.plugin.Plugin; import com.tencent.supersonic.chat.api.pojo.request.PluginQueryReq; import com.tencent.supersonic.chat.persistence.dataobject.PluginDO; @@ -14,19 +15,20 @@ import com.tencent.supersonic.chat.plugin.event.PluginDelEvent; import com.tencent.supersonic.chat.plugin.event.PluginUpdateEvent; import com.tencent.supersonic.chat.service.PluginService; import com.tencent.supersonic.chat.utils.ComponentFactory; +import com.tencent.supersonic.common.util.JsonUtil; import com.tencent.supersonic.semantic.api.model.response.DomainResp; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Optional; + +import java.util.*; import java.util.stream.Collectors; @Service +@Slf4j public class PluginServiceImpl implements PluginService { private PluginRepository pluginRepository; @@ -40,10 +42,12 @@ public class PluginServiceImpl implements PluginService { } @Override - public void createPlugin(Plugin plugin, User user){ + public synchronized void createPlugin(Plugin plugin, User user){ PluginDO pluginDO = convert(plugin, user); pluginRepository.createPlugin(pluginDO); - publisher.publishEvent(new PluginAddEvent(this, plugin)); + //compatible with H2 db + List plugins = getPluginList(); + publisher.publishEvent(new PluginAddEvent(this, plugins.get(plugins.size()-1))); } @Override @@ -93,6 +97,18 @@ public class PluginServiceImpl implements PluginService { if (StringUtils.isNotBlank(pluginQueryReq.getDomain())) { pluginDOExample.getOredCriteria().get(0).andDomainLike('%' + pluginQueryReq.getDomain() + '%'); } + if (StringUtils.isNotBlank(pluginQueryReq.getParseMode())) { + pluginDOExample.getOredCriteria().get(0).andParseModeEqualTo(pluginQueryReq.getParseMode()); + } + if (StringUtils.isNotBlank(pluginQueryReq.getName())) { + pluginDOExample.getOredCriteria().get(0).andNameLike('%' + pluginQueryReq.getName() + '%'); + } + if (StringUtils.isNotBlank(pluginQueryReq.getPattern())) { + pluginDOExample.getOredCriteria().get(0).andPatternLike('%' + pluginQueryReq.getPattern() + '%'); + } + if (StringUtils.isNotBlank(pluginQueryReq.getCreatedBy())) { + pluginDOExample.getOredCriteria().get(0).andCreatedByEqualTo(pluginQueryReq.getCreatedBy()); + } List pluginDOS = pluginRepository.query(pluginDOExample); if (StringUtils.isNotBlank(pluginQueryReq.getPattern())) { pluginDOS = pluginDOS.stream().filter(pluginDO -> @@ -105,8 +121,18 @@ public class PluginServiceImpl implements PluginService { @Override public Optional getPluginByName(String name) { + log.info("name:{}", name); return getPluginList().stream() - .filter(plugin -> plugin.getName().equalsIgnoreCase(name)) + .filter(plugin -> { + if (StringUtils.isBlank(plugin.getParseModeConfig())) { + return false; + } + PluginParseConfig functionCallConfig = JsonUtil.toObject(plugin.getParseModeConfig(), PluginParseConfig.class); + if (Objects.isNull(functionCallConfig)) { + return false; + } + return functionCallConfig.getName().equalsIgnoreCase(name); + }) .findFirst(); } @@ -120,8 +146,8 @@ public class PluginServiceImpl implements PluginService { List domainIdAuthorized = semanticLayer.getDomainListForAdmin().stream() .map(DomainResp::getId).collect(Collectors.toList()); plugins = plugins.stream().filter(plugin -> { - if (CollectionUtils.isEmpty(plugin.getDomainList())) { - return false; + if (CollectionUtils.isEmpty(plugin.getDomainList()) || plugin.isContainsAllDomain()) { + return true; } for (Long domainId : plugin.getDomainList()) { if (domainIdAuthorized.contains(domainId)) { @@ -136,8 +162,7 @@ public class PluginServiceImpl implements PluginService { public Plugin convert(PluginDO pluginDO){ Plugin plugin = new Plugin(); BeanUtils.copyProperties(pluginDO,plugin); - plugin.setParseMode(ParseMode.valueOf(pluginDO.getParseMode())); - if (pluginDO.getDomain() != null) { + if (StringUtils.isNotBlank(pluginDO.getDomain())) { plugin.setDomainList(Arrays.stream(pluginDO.getDomain().split(",")) .map(Long::parseLong).collect(Collectors.toList())); } @@ -152,7 +177,6 @@ public class PluginServiceImpl implements PluginService { pluginDO.setUpdatedAt(new Date()); pluginDO.setUpdatedBy(user.getName()); pluginDO.setDomain(StringUtils.join(plugin.getDomainList(), ",")); - pluginDO.setParseMode(plugin.getParseMode().name()); return pluginDO; } @@ -161,7 +185,6 @@ public class PluginServiceImpl implements PluginService { pluginDO.setUpdatedAt(new Date()); pluginDO.setUpdatedBy(user.getName()); pluginDO.setDomain(StringUtils.join(plugin.getDomainList(), ",")); - pluginDO.setParseMode(plugin.getParseMode().name()); return pluginDO; } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/QueryServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/QueryServiceImpl.java index ae4e51ae0..83df0ff3c 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/QueryServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/QueryServiceImpl.java @@ -6,15 +6,19 @@ import com.tencent.supersonic.chat.api.component.*; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.QueryContext; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.ExecuteQueryReq; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; +import com.tencent.supersonic.chat.api.pojo.response.ParseResp; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.api.pojo.response.QueryState; import com.tencent.supersonic.chat.query.QuerySelector; -import com.tencent.supersonic.chat.api.pojo.request.QueryDataRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryDataReq; import com.tencent.supersonic.chat.query.QueryManager; import com.tencent.supersonic.chat.service.ChatService; import com.tencent.supersonic.chat.service.QueryService; import com.tencent.supersonic.chat.utils.ComponentFactory; + +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -41,7 +45,82 @@ public class QueryServiceImpl implements QueryService { private QuerySelector querySelector = ComponentFactory.getQuerySelector(); @Override - public QueryResult executeQuery(QueryRequest queryReq) throws Exception { + public ParseResp performParsing(QueryReq queryReq) { + QueryContext queryCtx = new QueryContext(queryReq); + // in order to support multi-turn conversation, chat context is needed + ChatContext chatCtx = chatService.getOrCreateContext(queryReq.getChatId()); + + schemaMappers.stream().forEach(mapper -> { + mapper.map(queryCtx); + log.info("{} result:{}", mapper.getClass().getSimpleName(), JsonUtil.toString(queryCtx)); + }); + + semanticParsers.stream().forEach(parser -> { + parser.parse(queryCtx, chatCtx); + log.info("{} result:{}", parser.getClass().getSimpleName(), JsonUtil.toString(queryCtx)); + }); + + ParseResp parseResult; + if (queryCtx.getCandidateQueries().size() > 0) { + log.debug("pick before [{}]", queryCtx.getCandidateQueries().stream().collect( + Collectors.toList())); + List selectedQueries = querySelector.select(queryCtx.getCandidateQueries()); + log.debug("pick after [{}]", selectedQueries.stream().collect( + Collectors.toList())); + + List selectedParses = selectedQueries.stream() + .map(q -> q.getParseInfo()).collect(Collectors.toList()); + List candidateParses = queryCtx.getCandidateQueries().stream() + .map(q -> q.getParseInfo()).collect(Collectors.toList()); + + parseResult = ParseResp.builder() + .chatId(queryReq.getChatId()) + .queryText(queryReq.getQueryText()) + .state(selectedParses.size() > 1 ? ParseResp.ParseState.PENDING : ParseResp.ParseState.COMPLETED) + .selectedParses(selectedParses) + .candidateParses(candidateParses) + .build(); + } else { + parseResult = ParseResp.builder() + .chatId(queryReq.getChatId()) + .queryText(queryReq.getQueryText()) + .state(ParseResp.ParseState.FAILED) + .build(); + } + + return parseResult; + } + + @Override + public QueryResult performExecution(ExecuteQueryReq queryReq) throws Exception { + SemanticParseInfo parseInfo = queryReq.getParseInfo(); + SemanticQuery semanticQuery = QueryManager.createQuery(parseInfo.getQueryMode()); + if (semanticQuery == null) { + return null; + } + semanticQuery.setParseInfo(parseInfo); + + // in order to support multi-turn conversation, chat context is needed + ChatContext chatCtx = chatService.getOrCreateContext(queryReq.getChatId()); + + QueryResult queryResult = semanticQuery.execute(queryReq.getUser()); + if (queryResult != null) { + queryResult.setChatContext(parseInfo); + // update chat context after a successful semantic query + if (queryReq.isSaveAnswer() && QueryState.SUCCESS.equals(queryResult.getQueryState())) { + chatCtx.setParseInfo(parseInfo); + chatService.updateContext(chatCtx); + } + chatCtx.setQueryText(queryReq.getQueryText()); + chatCtx.setUser(queryReq.getUser().getName()); + chatService.addQuery(queryResult, chatCtx); + } + + return queryResult; + } + + @Override + public QueryResult executeQuery(QueryReq queryReq) throws Exception { QueryContext queryCtx = new QueryContext(queryReq); // in order to support multi-turn conversation, chat context is needed ChatContext chatCtx = chatService.getOrCreateContext(queryReq.getChatId()); @@ -60,17 +139,21 @@ public class QueryServiceImpl implements QueryService { if (queryCtx.getCandidateQueries().size() > 0) { log.info("pick before [{}]", queryCtx.getCandidateQueries().stream().collect( Collectors.toList())); - SemanticQuery semanticQuery = querySelector.select(queryCtx.getCandidateQueries()); - log.info("pick after [{}]", semanticQuery); + List selectedQueries = querySelector.select(queryCtx.getCandidateQueries()); + log.info("pick after [{}]", selectedQueries.stream().collect( + Collectors.toList())); + SemanticQuery semanticQuery = selectedQueries.get(0); queryResult = semanticQuery.execute(queryReq.getUser()); if (queryResult != null) { + chatCtx.setQueryText(queryReq.getQueryText()); // update chat context after a successful semantic query if (queryReq.isSaveAnswer() && QueryState.SUCCESS.equals(queryResult.getQueryState())) { - chatService.updateContext(chatCtx, queryCtx, semanticQuery.getParseInfo()); + chatCtx.setParseInfo(semanticQuery.getParseInfo()); + chatService.updateContext(chatCtx); } queryResult.setChatContext(chatCtx.getParseInfo()); - chatService.addQuery(queryResult, queryCtx, chatCtx); + chatService.addQuery(queryResult, chatCtx); } } @@ -78,13 +161,13 @@ public class QueryServiceImpl implements QueryService { } @Override - public SemanticParseInfo queryContext(QueryRequest queryCtx) { + public SemanticParseInfo queryContext(QueryReq queryCtx) { ChatContext context = chatService.getOrCreateContext(queryCtx.getChatId()); return context.getParseInfo(); } @Override - public QueryResult executeDirectQuery(QueryDataRequest queryData, User user) throws SqlParseException { + public QueryResult executeDirectQuery(QueryDataReq queryData, User user) throws SqlParseException { SemanticQuery semanticQuery = QueryManager.createRuleQuery(queryData.getQueryMode()); BeanUtils.copyProperties(queryData, semanticQuery.getParseInfo()); return semanticQuery.execute(user); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/RecommendServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/RecommendServiceImpl.java index ab314f84a..7ba8cc18d 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/RecommendServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/RecommendServiceImpl.java @@ -3,12 +3,12 @@ package com.tencent.supersonic.chat.service.impl; import com.tencent.supersonic.chat.api.pojo.DomainSchema; import com.tencent.supersonic.chat.api.pojo.SchemaElement; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; -import com.tencent.supersonic.chat.api.pojo.response.RecommendQuestion; -import com.tencent.supersonic.chat.config.ChatConfigFilter; -import com.tencent.supersonic.chat.config.ChatConfigResp; -import com.tencent.supersonic.chat.config.ChatConfigRich; -import com.tencent.supersonic.chat.api.pojo.response.RecommendResponse; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; +import com.tencent.supersonic.chat.api.pojo.response.RecommendQuestionResp; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigFilter; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; +import com.tencent.supersonic.chat.api.pojo.response.RecommendResp; import java.util.ArrayList; import java.util.Comparator; @@ -37,14 +37,14 @@ public class RecommendServiceImpl implements RecommendService { private SemanticService semanticService; @Override - public RecommendResponse recommend(QueryRequest queryCtx, Long limit) { + public RecommendResp recommend(QueryReq queryCtx, Long limit) { if (Objects.isNull(limit) || limit <= 0) { limit = Long.MAX_VALUE; } log.debug("limit:{}", limit); Long domainId = queryCtx.getDomainId(); if (Objects.isNull(domainId)) { - return new RecommendResponse(); + return new RecommendResp(); } DomainSchema domainSchema = semanticService.getDomainSchema(domainId); @@ -75,21 +75,21 @@ public class RecommendServiceImpl implements RecommendService { return item; }).collect(Collectors.toList()); - RecommendResponse response = new RecommendResponse(); + RecommendResp response = new RecommendResp(); response.setDimensions(dimensions); response.setMetrics(metrics); return response; } @Override - public RecommendResponse recommendMetricMode(QueryRequest queryCtx, Long limit) { - RecommendResponse recommendResponse = recommend(queryCtx, limit); + public RecommendResp recommendMetricMode(QueryReq queryCtx, Long limit) { + RecommendResp recommendResponse = recommend(queryCtx, limit); // filter black Item if (Objects.isNull(recommendResponse)) { return recommendResponse; } - ChatConfigRich chatConfigRich = configService.getConfigRichInfo(Long.valueOf(queryCtx.getDomainId())); + ChatConfigRichResp chatConfigRich = configService.getConfigRichInfo(Long.valueOf(queryCtx.getDomainId())); if (Objects.nonNull(chatConfigRich) && Objects.nonNull(chatConfigRich.getChatAggRichConfig()) && Objects.nonNull(chatConfigRich.getChatAggRichConfig().getVisibility())) { List blackMetricIdList = chatConfigRich.getChatAggRichConfig().getVisibility().getBlackMetricIdList(); @@ -105,15 +105,15 @@ public class RecommendServiceImpl implements RecommendService { } @Override - public List recommendQuestion(Long domainId) { - List recommendQuestions = new ArrayList<>(); + public List recommendQuestion(Long domainId) { + List recommendQuestions = new ArrayList<>(); ChatConfigFilter chatConfigFilter = new ChatConfigFilter(); chatConfigFilter.setDomainId(domainId); List chatConfigRespList = configService.search(chatConfigFilter, null); if (!CollectionUtils.isEmpty(chatConfigRespList)) { chatConfigRespList.stream().forEach(chatConfigResp -> { if (Objects.nonNull(chatConfigResp) && !CollectionUtils.isEmpty(chatConfigResp.getRecommendedQuestions())) { - recommendQuestions.add(new RecommendQuestion(chatConfigResp.getDomainId(), chatConfigResp.getRecommendedQuestions())); + recommendQuestions.add(new RecommendQuestionResp(chatConfigResp.getDomainId(), chatConfigResp.getRecommendedQuestions())); } }); return recommendQuestions; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/SearchServiceImpl.java b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/SearchServiceImpl.java index 745c1c337..1fac983c3 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/SearchServiceImpl.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/service/impl/SearchServiceImpl.java @@ -7,7 +7,7 @@ import com.tencent.supersonic.chat.api.pojo.SchemaElementType; import com.tencent.supersonic.chat.api.pojo.SemanticSchema; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.chat.api.pojo.request.QueryFilters; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.api.pojo.response.SearchResult; import com.tencent.supersonic.chat.mapper.DomainInfoStat; import com.tencent.supersonic.chat.mapper.DomainWithSemanticType; @@ -55,7 +55,7 @@ public class SearchServiceImpl implements SearchService { private SearchMatchStrategy searchMatchStrategy; @Override - public List search(QueryRequest queryCtx) { + public List search(QueryReq queryCtx) { String queryText = queryCtx.getQueryText(); // 1.get meta info SemanticSchema semanticSchemaDb = schemaService.getSemanticSchema(); @@ -109,8 +109,8 @@ public class SearchServiceImpl implements SearchService { return searchResults.stream().limit(RESULT_SIZE).collect(Collectors.toList()); } - private List getPossibleDomains(QueryRequest queryCtx, List originals, - DomainInfoStat domainStat, Long webDomainId) { + private List getPossibleDomains(QueryReq queryCtx, List originals, + DomainInfoStat domainStat, Long webDomainId) { if (Objects.nonNull(webDomainId) && webDomainId > 0) { List result = new ArrayList<>(); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/CacheUtils.java b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/CacheUtils.java index 1062f27eb..c6bec3994 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/CacheUtils.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/CacheUtils.java @@ -14,11 +14,11 @@ public class CacheUtils { .maximumSize(1000) .build(); public static void put(QueryContext queryContext, ChatContext chatCtx, Object v){ - String key=chatCtx.getUser()+"_"+chatCtx.getChatId()+"_"+queryContext.getRequest().getQueryText(); + String key=chatCtx.getUser()+"_"+chatCtx.getChatId()+"_"+ queryContext.getRequest().getQueryText(); cache.put(key,v); } - public static Object get(QueryContext queryContext,ChatContext chatCtx){ - String key=chatCtx.getUser()+"_"+chatCtx.getChatId()+"_"+queryContext.getRequest().getQueryText(); + public static Object get(QueryContext queryContext, ChatContext chatCtx){ + String key=chatCtx.getUser()+"_"+chatCtx.getChatId()+"_"+ queryContext.getRequest().getQueryText(); return cache.getIfPresent(key); } } diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatConfigHelper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatConfigHelper.java index 28a2f59bb..e2a823f04 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatConfigHelper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatConfigHelper.java @@ -5,7 +5,8 @@ import static com.tencent.supersonic.common.pojo.Constants.ADMIN_LOWER; import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.DomainSchema; import com.tencent.supersonic.chat.api.pojo.SchemaElement; -import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestion; +import com.tencent.supersonic.chat.api.pojo.request.*; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; import com.tencent.supersonic.chat.config.*; import com.tencent.supersonic.chat.persistence.dataobject.ChatConfigDO; import com.tencent.supersonic.common.pojo.enums.StatusEnum; @@ -103,9 +104,9 @@ public class ChatConfigHelper { BeanUtils.copyProperties(chatConfigDO, chatConfigDescriptor); - chatConfigDescriptor.setChatDetailConfig(JsonUtil.toObject(chatConfigDO.getChatDetailConfig(), ChatDetailConfig.class)); - chatConfigDescriptor.setChatAggConfig(JsonUtil.toObject(chatConfigDO.getChatAggConfig(), ChatAggConfig.class)); - chatConfigDescriptor.setRecommendedQuestions(JsonUtil.toList(chatConfigDO.getRecommendedQuestions(), RecommendedQuestion.class)); + chatConfigDescriptor.setChatDetailConfig(JsonUtil.toObject(chatConfigDO.getChatDetailConfig(), ChatDetailConfigReq.class)); + chatConfigDescriptor.setChatAggConfig(JsonUtil.toObject(chatConfigDO.getChatAggConfig(), ChatAggConfigReq.class)); + chatConfigDescriptor.setRecommendedQuestions(JsonUtil.toList(chatConfigDO.getRecommendedQuestions(), RecommendedQuestionReq.class)); chatConfigDescriptor.setStatusEnum(StatusEnum.of(chatConfigDO.getStatus())); chatConfigDescriptor.setCreatedBy(chatConfigDO.getCreatedBy()); @@ -132,15 +133,15 @@ public class ChatConfigHelper { return chatConfigResp; } - private ChatDetailConfig generateEmptyChatDetailConfigResp() { - ChatDetailConfig chatDetailConfig = new ChatDetailConfig(); + private ChatDetailConfigReq generateEmptyChatDetailConfigResp() { + ChatDetailConfigReq chatDetailConfig = new ChatDetailConfigReq(); ItemVisibility visibility = new ItemVisibility(); chatDetailConfig.setVisibility(visibility); return chatDetailConfig; } - private ChatAggConfig generateEmptyChatAggConfigResp() { - ChatAggConfig chatAggConfig = new ChatAggConfig(); + private ChatAggConfigReq generateEmptyChatAggConfigResp() { + ChatAggConfigReq chatAggConfig = new ChatAggConfigReq(); ItemVisibility visibility = new ItemVisibility(); chatAggConfig.setVisibility(visibility); return chatAggConfig; diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatGptHelper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatGptHelper.java new file mode 100644 index 000000000..b7672c7b6 --- /dev/null +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/ChatGptHelper.java @@ -0,0 +1,78 @@ +package com.tencent.supersonic.chat.utils; + + +import com.plexpt.chatgpt.ChatGPT; +import com.plexpt.chatgpt.entity.chat.ChatCompletion; +import com.plexpt.chatgpt.entity.chat.ChatCompletionResponse; +import com.plexpt.chatgpt.entity.chat.Message; +import com.plexpt.chatgpt.util.Proxys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.net.Proxy; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; + + +@Component +public class ChatGptHelper { + + @Value("${llm.chatgpt.apikey:sk-kdgPxxx}") + private String apiKey; + + @Value("${llm.chatgpt.apiHost:https://api.openai.com/}") + private String apiHost; + + @Value("${llm.chatgpt.proxyIp:default}") + private String proxyIp; + + @Value("${llm.chatgpt.proxyPort:8080}") + private Integer proxyPort; + + + public ChatGPT getChatGPT(){ + Proxy proxy = null; + if (!"default".equals(proxyIp)){ + proxy = Proxys.http(proxyIp, proxyPort); + } + return ChatGPT.builder() + .apiKey(apiKey) + .proxy(proxy) + .timeout(900) + .apiHost(apiHost) //反向代理地址 + .build() + .init(); + } + + public String inferredTime(String queryText){ + long nowTime = System.currentTimeMillis(); + Date date = new Date(nowTime); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + String formattedDate = sdf.format(date); + Message system = Message.ofSystem("现在时间 "+formattedDate+",你是一个专业的数据分析师,你的任务是基于数据,专业的解答用户的问题。" + + "你需要遵守以下规则:\n" + + "1.返回规范的数据格式,json,如: 输入:近 10 天的日活跃数,输出:{\"start\":\"2023-07-21\",\"end\":\"2023-07-31\"}" + + "2.你对时间数据要求规范,能从近 10 天,国庆节,端午节,获取到相应的时间,填写到 json 中。\n"+ + "3.你的数据时间,只有当前及之前时间即可,超过则回复去年\n" + + "4.只需要解析出时间,时间可以是时间月和年或日、日历采用公历\n"+ + "5.时间给出要是绝对正确,不能瞎编\n" + ); + Message message = Message.of("输入:"+queryText+",输出:"); + ChatCompletion chatCompletion = ChatCompletion.builder() + .model(ChatCompletion.Model.GPT_3_5_TURBO_16K.getName()) + .messages(Arrays.asList(system, message)) + .maxTokens(10000) + .temperature(0.9) + .build(); + ChatCompletionResponse response = getChatGPT().chatCompletion(chatCompletion); + Message res = response.getChoices().get(0).getMessage(); + return res.getContent(); + } + + public static void main(String[] args) { + + } + + +} diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictMetaHelper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictMetaHelper.java index ae8abf276..5e62d1754 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictMetaHelper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictMetaHelper.java @@ -6,6 +6,10 @@ import static com.tencent.supersonic.common.pojo.Constants.UNDERLINE; import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.chat.api.pojo.DomainSchema; import com.tencent.supersonic.chat.api.pojo.SchemaElement; +import com.tencent.supersonic.chat.api.pojo.request.KnowledgeAdvancedConfig; +import com.tencent.supersonic.chat.api.pojo.request.KnowledgeInfoReq; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatDefaultRichConfigResp; import com.tencent.supersonic.chat.config.*; import com.tencent.supersonic.chat.service.ConfigService; import com.tencent.supersonic.chat.persistence.dataobject.DimValueDO; @@ -124,13 +128,13 @@ public class DictMetaHelper { private void fillDimValueDOList(List dimValueDOList, Long domainId, Map dimIdAndDescPair) { - ChatConfigRich chaConfigRichDesc = configService.getConfigRichInfo(domainId); + ChatConfigRichResp chaConfigRichDesc = configService.getConfigRichInfo(domainId); if (Objects.nonNull(chaConfigRichDesc) && Objects.nonNull(chaConfigRichDesc.getChatAggRichConfig())) { - ChatDefaultRichConfig chatDefaultConfig = chaConfigRichDesc.getChatAggRichConfig().getChatDefaultConfig(); - List knowledgeAggInfo = chaConfigRichDesc.getChatAggRichConfig().getKnowledgeInfos(); + ChatDefaultRichConfigResp chatDefaultConfig = chaConfigRichDesc.getChatAggRichConfig().getChatDefaultConfig(); + List knowledgeAggInfo = chaConfigRichDesc.getChatAggRichConfig().getKnowledgeInfos(); - List knowledgeDetailInfo = chaConfigRichDesc.getChatDetailRichConfig().getKnowledgeInfos(); + List knowledgeDetailInfo = chaConfigRichDesc.getChatDetailRichConfig().getKnowledgeInfos(); fillKnowledgeDimValue(knowledgeDetailInfo, chatDefaultConfig, dimValueDOList, dimIdAndDescPair, domainId); fillKnowledgeDimValue(knowledgeAggInfo, chatDefaultConfig, dimValueDOList, dimIdAndDescPair, domainId); @@ -139,7 +143,7 @@ public class DictMetaHelper { } } - private void fillKnowledgeDimValue(List knowledgeInfos, ChatDefaultRichConfig chatDefaultConfig, + private void fillKnowledgeDimValue(List knowledgeInfos, ChatDefaultRichConfigResp chatDefaultConfig, List dimValueDOList, Map dimIdAndDescPair, Long domainId) { if (!CollectionUtils.isEmpty(knowledgeInfos)) { List dimensions = new ArrayList<>(); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictQueryHelper.java b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictQueryHelper.java index fe7bae341..66c1e471f 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictQueryHelper.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/DictQueryHelper.java @@ -91,7 +91,7 @@ public class DictQueryHelper { } private List generateFileData(List> resultList, String nature, String dimName, - String metricName) { + String metricName) { List data = new ArrayList<>(); if (CollectionUtils.isEmpty(resultList)) { return data; @@ -160,7 +160,7 @@ public class DictQueryHelper { queryStructCmd.setOrders(orders); DateConf dateInfo = new DateConf(); - dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + dateInfo.setDateMode(DateConf.DateMode.RECENT); dateInfo.setUnit(defaultMetricDesc.getUnit()); queryStructCmd.setDateInfo(dateInfo); diff --git a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/QueryReqBuilder.java b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/QueryReqBuilder.java index 44a1875e8..82213445b 100644 --- a/chat/core/src/main/java/com/tencent/supersonic/chat/utils/QueryReqBuilder.java +++ b/chat/core/src/main/java/com/tencent/supersonic/chat/utils/QueryReqBuilder.java @@ -3,9 +3,7 @@ package com.tencent.supersonic.chat.utils; import com.google.common.collect.Lists; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.query.rule.metric.MetricDomainQuery; -import com.tencent.supersonic.chat.query.rule.metric.MetricFilterQuery; -import com.tencent.supersonic.chat.query.rule.metric.MetricGroupByQuery; +import com.tencent.supersonic.chat.query.QueryManager; import com.tencent.supersonic.common.pojo.Aggregator; import com.tencent.supersonic.common.pojo.Constants; import com.tencent.supersonic.common.pojo.DateConf; @@ -17,11 +15,8 @@ import com.tencent.supersonic.semantic.api.query.pojo.Filter; import com.tencent.supersonic.semantic.api.query.request.QueryDslReq; import com.tencent.supersonic.semantic.api.query.request.QueryMultiStructReq; import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.time.LocalDate; +import java.util.*; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; @@ -35,7 +30,7 @@ public class QueryReqBuilder { QueryStructReq queryStructCmd = new QueryStructReq(); queryStructCmd.setDomainId(parseInfo.getDomainId()); queryStructCmd.setNativeQuery(parseInfo.getNativeQuery()); - queryStructCmd.setDateInfo(parseInfo.getDateInfo()); + queryStructCmd.setDateInfo(rewrite2Between(parseInfo.getDateInfo())); List dimensionFilters = parseInfo.getDimensionFilters().stream() .filter(chatFilter -> Strings.isNotEmpty(chatFilter.getBizName())) @@ -49,16 +44,47 @@ public class QueryReqBuilder { queryStructCmd.setMetricFilters(metricFilters); addDateDimension(parseInfo); - List dimensions = parseInfo.getDimensions().stream().map(entry -> entry.getBizName()) + List dimensions = parseInfo.getDimensions().stream().map(SchemaElement::getBizName) .collect(Collectors.toList()); queryStructCmd.setGroups(dimensions); queryStructCmd.setLimit(parseInfo.getLimit()); - Set order = getOrder(parseInfo.getOrders(), parseInfo.getAggType(), parseInfo.getMetrics()); - queryStructCmd.setOrders(new ArrayList<>(order)); - queryStructCmd.setAggregators(getAggregatorByMetric(parseInfo.getMetrics(), parseInfo.getAggType())); + // only one metric is queried at once + Set metrics = parseInfo.getMetrics(); + if (!CollectionUtils.isEmpty(metrics)) { + SchemaElement metricElement = parseInfo.getMetrics().iterator().next(); + Set order = getOrder(parseInfo.getOrders(), parseInfo.getAggType(), metricElement); + queryStructCmd.setAggregators(getAggregatorByMetric(parseInfo.getAggType(), metricElement)); + queryStructCmd.setOrders(new ArrayList<>(order)); + } + + deletionDuplicated(queryStructCmd); + return queryStructCmd; } + private static void deletionDuplicated(QueryStructReq queryStructReq) { + if (!CollectionUtils.isEmpty(queryStructReq.getGroups()) && queryStructReq.getGroups().size() > 1) { + Set groups = new HashSet<>(); + groups.addAll(queryStructReq.getGroups()); + queryStructReq.getGroups().clear(); + queryStructReq.getGroups().addAll(groups); + } + } + + private static DateConf rewrite2Between(DateConf dateInfo) { + DateConf dateInfoNew = new DateConf(); + BeanUtils.copyProperties(dateInfo, dateInfoNew); + if (Objects.nonNull(dateInfo) && DateConf.DateMode.RECENT.equals(dateInfo.getDateMode())) { + int unit = dateInfo.getUnit(); + String startDate = LocalDate.now().plusDays(-unit).toString(); + String endDate = LocalDate.now().plusDays(-1).toString(); + dateInfoNew.setDateMode(DateConf.DateMode.BETWEEN); + dateInfoNew.setStartDate(startDate); + dateInfoNew.setEndDate(endDate); + } + return dateInfoNew; + } + public static QueryMultiStructReq buildMultiStructReq(SemanticParseInfo parseInfo) { QueryStructReq queryStructReq = buildStructReq(parseInfo); QueryMultiStructReq queryMultiStructReq = new QueryMultiStructReq(); @@ -75,11 +101,12 @@ public class QueryReqBuilder { /** * convert to QueryDslReq + * * @param querySql * @param domainId * @return */ - public static QueryDslReq buildDslReq(String querySql,Long domainId) { + public static QueryDslReq buildDslReq(String querySql, Long domainId) { QueryDslReq queryDslReq = new QueryDslReq(); if (Objects.nonNull(querySql)) { queryDslReq.setSql(querySql); @@ -89,11 +116,11 @@ public class QueryReqBuilder { } - private static List getAggregatorByMetric(Set metrics, AggregateTypeEnum aggregateType) { + private static List getAggregatorByMetric(AggregateTypeEnum aggregateType, SchemaElement metric) { List aggregators = new ArrayList<>(); - String agg = (aggregateType == null || aggregateType.equals(AggregateTypeEnum.NONE)) ? "" - : aggregateType.name(); - for (SchemaElement metric : metrics) { + if(metric != null) { + String agg = (aggregateType == null || aggregateType.equals(AggregateTypeEnum.NONE)) ? "" + : aggregateType.name(); aggregators.add(new Aggregator(metric.getBizName(), AggOperatorEnum.of(agg))); } return aggregators; @@ -118,41 +145,42 @@ public class QueryReqBuilder { return; } } + + if (Objects.nonNull(parseInfo.getAggType()) && !parseInfo.getAggType().equals(AggregateTypeEnum.NONE)) { + return; + } + SchemaElement dimension = new SchemaElement(); dimension.setBizName(dateField); - if (MetricDomainQuery.QUERY_MODE.equals(queryMode) - || MetricGroupByQuery.QUERY_MODE.equals(queryMode) - || MetricFilterQuery.QUERY_MODE.equals(queryMode) - ) { + if (QueryManager.isMetricQuery(queryMode)) { parseInfo.getDimensions().add(dimension); } } } - public static Set getOrder(Set parseOrder, AggregateTypeEnum aggregator, Set metrics) { + public static Set getOrder(Set parseOrder, AggregateTypeEnum aggregator, SchemaElement metric) { if (!CollectionUtils.isEmpty(parseOrder)) { return parseOrder; } Set orders = new LinkedHashSet(); - if (CollectionUtils.isEmpty(metrics)) { + if (metric == null) { return orders; } + if ((AggregateTypeEnum.TOPN.equals(aggregator) || AggregateTypeEnum.MAX.equals(aggregator) || AggregateTypeEnum.MIN.equals( aggregator))) { - for (SchemaElement metric : metrics) { - Order order = new Order(); - order.setColumn(metric.getBizName()); - order.setDirection("desc"); - orders.add(order); - } + Order order = new Order(); + order.setColumn(metric.getBizName()); + order.setDirection("desc"); + orders.add(order); } return orders; } public static String getDateField(DateConf dateConf) { - if(Objects.isNull(dateConf)) { + if (Objects.isNull(dateConf)) { return ""; } String dateField = TimeDimensionEnum.DAY.getName(); @@ -165,7 +193,7 @@ public class QueryReqBuilder { return dateField; } - public static QueryStructReq buildStructRatioReq(SemanticParseInfo parseInfo,SchemaElement metric,AggOperatorEnum aggOperatorEnum) { + public static QueryStructReq buildStructRatioReq(SemanticParseInfo parseInfo, SchemaElement metric, AggOperatorEnum aggOperatorEnum) { QueryStructReq queryStructCmd = buildStructReq(parseInfo); queryStructCmd.setNativeQuery(false); queryStructCmd.setOrders(new ArrayList<>()); diff --git a/chat/core/src/main/resources/mapper/PluginDOMapper.xml b/chat/core/src/main/resources/mapper/PluginDOMapper.xml index 0fe6e3f5e..b9b4842e7 100644 --- a/chat/core/src/main/resources/mapper/PluginDOMapper.xml +++ b/chat/core/src/main/resources/mapper/PluginDOMapper.xml @@ -14,7 +14,9 @@ + + @@ -50,7 +52,7 @@ updated_by - config + parse_mode_config, config, comment @@ -216,9 +232,15 @@ updated_by = #{updatedBy,jdbcType=VARCHAR}, + + parse_mode_config = #{parseModeConfig,jdbcType=LONGVARCHAR}, + config = #{config,jdbcType=LONGVARCHAR}, + + comment = #{comment,jdbcType=LONGVARCHAR}, + where id = #{id,jdbcType=BIGINT} @@ -233,7 +255,9 @@ created_by = #{createdBy,jdbcType=VARCHAR}, updated_at = #{updatedAt,jdbcType=TIMESTAMP}, updated_by = #{updatedBy,jdbcType=VARCHAR}, - config = #{config,jdbcType=LONGVARCHAR} + parse_mode_config = #{parseModeConfig,jdbcType=LONGVARCHAR}, + config = #{config,jdbcType=LONGVARCHAR}, + comment = #{comment,jdbcType=LONGVARCHAR} where id = #{id,jdbcType=BIGINT} diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeRangeParserTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeRangeParserTest.java index 23d916e33..52d0ba795 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeRangeParserTest.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/application/parser/TimeRangeParserTest.java @@ -3,7 +3,7 @@ package com.tencent.supersonic.chat.application.parser; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.QueryContext; import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.parser.rule.TimeRangeParser; import org.junit.jupiter.api.Test; @@ -27,7 +27,7 @@ class TimeRangeParserTest { void parse() { TimeRangeParser timeRangeParser = new TimeRangeParser(); - QueryRequest queryRequest = new QueryRequest(); + QueryReq queryRequest = new QueryReq(); ChatContext chatCtx = new ChatContext(); SchemaMapInfo schemaMap = new SchemaMapInfo(); diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/mapper/HanlpDictMapperTest.java b/chat/core/src/test/java/com/tencent/supersonic/chat/mapper/HanlpDictMapperTest.java index 470215470..f96174c50 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/mapper/HanlpDictMapperTest.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/mapper/HanlpDictMapperTest.java @@ -1,7 +1,7 @@ package com.tencent.supersonic.chat.mapper; import com.tencent.supersonic.chat.api.pojo.QueryContext; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.test.context.ContextTest; import org.junit.jupiter.api.Test; @@ -12,7 +12,7 @@ class HanlpDictMapperTest extends ContextTest { @Test void map() { - QueryRequest queryRequest = new QueryRequest(); + QueryReq queryRequest = new QueryReq(); queryRequest.setChatId(1); queryRequest.setDomainId(2L); queryRequest.setQueryText("supersonic按部门访问次数"); diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java index e79eaf500..c00439b44 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/MockBeansConfiguration.java @@ -6,6 +6,9 @@ import static org.mockito.Mockito.when; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.component.SemanticLayer; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigRichResp; +import com.tencent.supersonic.chat.api.pojo.response.EntityRichInfoResp; import com.tencent.supersonic.chat.config.*; import com.tencent.supersonic.chat.persistence.repository.impl.ChatContextRepositoryImpl; import com.tencent.supersonic.chat.service.QueryService; @@ -40,12 +43,12 @@ public class MockBeansConfiguration { public static void buildHttpSemanticServiceImpl(SemanticLayer httpSemanticLayer, List dimensionDescs, List metricDescs) { - ChatConfigRich chaConfigRichDesc = new ChatConfigRich(); + ChatConfigRichResp chaConfigRichDesc = new ChatConfigRichResp(); DefaultMetric defaultMetricDesc = new DefaultMetric(); defaultMetricDesc.setUnit(3); defaultMetricDesc.setPeriod(Constants.DAY); // chaConfigRichDesc.setDefaultMetrics(new ArrayList<>(Arrays.asList(defaultMetricDesc))); - EntityRichInfo entityDesc = new EntityRichInfo(); + EntityRichInfoResp entityDesc = new EntityRichInfoResp(); List dimensionDescs1 = new ArrayList<>(); DimSchemaResp dimensionDesc = new DimSchemaResp(); dimensionDesc.setId(162L); diff --git a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/SemanticParseObjectHelper.java b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/SemanticParseObjectHelper.java index 16fad98df..e60fde11c 100644 --- a/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/SemanticParseObjectHelper.java +++ b/chat/core/src/test/java/com/tencent/supersonic/chat/test/context/SemanticParseObjectHelper.java @@ -66,7 +66,7 @@ public class SemanticParseObjectHelper { if (dayAgo > 0) { DateConf dateInfo = new DateConf(); dateInfo.setUnit(dayAgo); - dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS); + dateInfo.setDateMode(DateConf.DateMode.RECENT); return dateInfo; } return null; diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/dictionary/builder/EntityWordBuilder.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/dictionary/builder/EntityWordBuilder.java index 98a2ae368..cd5f60c68 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/dictionary/builder/EntityWordBuilder.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/dictionary/builder/EntityWordBuilder.java @@ -3,12 +3,14 @@ package com.tencent.supersonic.knowledge.dictionary.builder; import com.google.common.collect.Lists; import java.util.List; +import java.util.Objects; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.knowledge.dictionary.DictWord; import com.tencent.supersonic.knowledge.dictionary.DictWordType; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; /** * dimension value wordNature @@ -20,13 +22,23 @@ public class EntityWordBuilder extends BaseWordBuilder { @Override public List doGet(String word, SchemaElement schemaElement) { List result = Lists.newArrayList(); - DictWord dictWord = new DictWord(); - dictWord.setWord(word); + + if (Objects.isNull(schemaElement)) { + return result; + } + Long domain = schemaElement.getDomain(); String nature = DictWordType.NATURE_SPILT + domain + DictWordType.NATURE_SPILT + schemaElement.getId() + DictWordType.ENTITY.getType(); - dictWord.setNatureWithFrequency(String.format("%s " + DEFAULT_FREQUENCY * 2, nature)); - result.add(dictWord); + + if (!CollectionUtils.isEmpty(schemaElement.getAlias())) { + schemaElement.getAlias().stream().forEach(alias -> { + DictWord dictWordAlias = new DictWord(); + dictWordAlias.setWord(alias); + dictWordAlias.setNatureWithFrequency(String.format("%s " + DEFAULT_FREQUENCY * 2, nature)); + result.add(dictWordAlias); + }); + } return result; } diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/BaseSemanticLayer.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/BaseSemanticLayer.java index 56ad0ecbe..2ef4b38b4 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/BaseSemanticLayer.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/BaseSemanticLayer.java @@ -4,19 +4,13 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.tencent.supersonic.chat.api.component.SemanticLayer; import com.tencent.supersonic.chat.api.pojo.DomainSchema; -import com.tencent.supersonic.common.pojo.Aggregator; -import com.tencent.supersonic.common.pojo.Order; import com.tencent.supersonic.common.pojo.ResultData; import com.tencent.supersonic.semantic.api.model.response.DomainSchemaResp; import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp; -import com.tencent.supersonic.semantic.api.query.request.QueryStructReq; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; @@ -76,31 +70,5 @@ public abstract class BaseSemanticLayer implements SemanticLayer { return domainSchemaList; } - protected void deletionDuplicated(QueryStructReq queryStructReq) { - if (!CollectionUtils.isEmpty(queryStructReq.getGroups()) && queryStructReq.getGroups().size() > 1) { - Set groups = new HashSet<>(); - groups.addAll(queryStructReq.getGroups()); - queryStructReq.getGroups().clear(); - queryStructReq.getGroups().addAll(groups); - } - } - - protected void onlyQueryFirstMetric(QueryStructReq queryStructReq) { - if (!CollectionUtils.isEmpty(queryStructReq.getAggregators()) && queryStructReq.getAggregators().size() > 1) { - log.info("multi metric in aggregators:{} , only query first one", queryStructReq.getAggregators()); - List aggregators = queryStructReq.getAggregators().subList(0, 1); - List excludeAggregators = queryStructReq.getAggregators().stream().map(a -> a.getColumn()) - .filter(a -> !a.equals(aggregators.get(0).getColumn())).collect( - Collectors.toList()); - queryStructReq.setAggregators(aggregators); - List orders = queryStructReq.getOrders().stream() - .filter(o -> !excludeAggregators.contains(o.getColumn())).collect( - Collectors.toList()); - log.info("multi metric in orders:{} ", queryStructReq.getOrders()); - queryStructReq.setOrders(orders); - - } - } - protected abstract List doFetchDomainSchema(List ids); } diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/DomainSchemaBuilder.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/DomainSchemaBuilder.java index f6a4c2e16..0417830a8 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/DomainSchemaBuilder.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/DomainSchemaBuilder.java @@ -4,6 +4,7 @@ import com.tencent.supersonic.chat.api.pojo.DomainSchema; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SchemaElementType; import com.tencent.supersonic.semantic.api.model.pojo.DimValueMap; +import com.tencent.supersonic.semantic.api.model.pojo.Entity; import com.tencent.supersonic.semantic.api.model.response.DimSchemaResp; import com.tencent.supersonic.semantic.api.model.response.DomainSchemaResp; import com.tencent.supersonic.semantic.api.model.response.MetricSchemaResp; @@ -13,6 +14,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.util.CollectionUtils; import java.util.*; +import java.util.stream.Collectors; public class DomainSchemaBuilder { @@ -100,18 +102,21 @@ public class DomainSchemaBuilder { domainSchema.getDimensions().addAll(dimensions); domainSchema.getDimensionValues().addAll(dimensionValues); - if (!CollectionUtils.isEmpty(resp.getEntityNames())) { - Set entities = new HashSet<>(); - for (String entity : resp.getEntityNames()) { - entities.add(SchemaElement.builder() - .domain(resp.getId()) - .id(resp.getId()) - .name(entity) - .bizName(entity) - .type(SchemaElementType.ENTITY) - .build()); + + Entity entity = resp.getEntity(); + if (Objects.nonNull(entity)) { + SchemaElement entityElement = new SchemaElement(); + + if (!CollectionUtils.isEmpty(entity.getNames()) && Objects.nonNull(entity.getEntityId())) { + Map idAndDimPair = dimensions.stream() + .collect(Collectors.toMap(SchemaElement::getId, schemaElement -> schemaElement,(k1,k2)->k2)); + if (idAndDimPair.containsKey(entity.getEntityId())) { + entityElement = idAndDimPair.get(entity.getEntityId()); + entityElement.setType(SchemaElementType.ENTITY); + } + entityElement.setAlias(entity.getNames()); + domainSchema.setEntity(entityElement); } - domainSchema.getEntities().addAll(entities); } return domainSchema; diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/LocalSemanticLayer.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/LocalSemanticLayer.java index 9cc896198..168d448e6 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/LocalSemanticLayer.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/LocalSemanticLayer.java @@ -36,8 +36,6 @@ public class LocalSemanticLayer extends BaseSemanticLayer { @Override public QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user) { - deletionDuplicated(queryStructReq); - onlyQueryFirstMetric(queryStructReq); try { QueryService queryService = ContextUtils.getBean(QueryService.class); QueryResultWithSchemaResp queryResultWithSchemaResp = queryService.queryByStruct(queryStructReq, user); @@ -50,10 +48,6 @@ public class LocalSemanticLayer extends BaseSemanticLayer { @Override public QueryResultWithSchemaResp queryByMultiStruct(QueryMultiStructReq queryMultiStructReq, User user) { - for (QueryStructReq queryStructReq : queryMultiStructReq.getQueryStructReqs()) { - deletionDuplicated(queryStructReq); - onlyQueryFirstMetric(queryStructReq); - } try { QueryService queryService = ContextUtils.getBean(QueryService.class); return queryService.queryByMultiStruct(queryMultiStructReq, user); diff --git a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/RemoteSemanticLayer.java b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/RemoteSemanticLayer.java index 7a229b1ba..10be3de0a 100644 --- a/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/RemoteSemanticLayer.java +++ b/chat/knowledge/src/main/java/com/tencent/supersonic/knowledge/semantic/RemoteSemanticLayer.java @@ -58,8 +58,6 @@ public class RemoteSemanticLayer extends BaseSemanticLayer { @Override public QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user) { - deletionDuplicated(queryStructReq); - onlyQueryFirstMetric(queryStructReq); DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); return searchByRestTemplate( defaultSemanticConfig.getSemanticUrl() + defaultSemanticConfig.getSearchByStructPath(), @@ -68,10 +66,6 @@ public class RemoteSemanticLayer extends BaseSemanticLayer { @Override public QueryResultWithSchemaResp queryByMultiStruct(QueryMultiStructReq queryMultiStructReq, User user) { - for (QueryStructReq queryStructReq : queryMultiStructReq.getQueryStructReqs()) { - deletionDuplicated(queryStructReq); - onlyQueryFirstMetric(queryStructReq); - } DefaultSemanticConfig defaultSemanticConfig = ContextUtils.getBean(DefaultSemanticConfig.class); return searchByRestTemplate( defaultSemanticConfig.getSemanticUrl() + defaultSemanticConfig.getSearchByMultiStructPath(), diff --git a/common/src/main/java/com/tencent/supersonic/common/pojo/DateConf.java b/common/src/main/java/com/tencent/supersonic/common/pojo/DateConf.java index a3f0d292c..39b3c1d82 100644 --- a/common/src/main/java/com/tencent/supersonic/common/pojo/DateConf.java +++ b/common/src/main/java/com/tencent/supersonic/common/pojo/DateConf.java @@ -4,6 +4,7 @@ import static java.time.LocalDate.now; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import lombok.Data; @@ -13,7 +14,7 @@ public class DateConf { private static final long serialVersionUID = 3074129990945004340L; - private DateMode dateMode = DateMode.RECENT_UNITS; + private DateMode dateMode = DateMode.RECENT; /** * like 2021-10-22, dateMode=1 @@ -42,15 +43,32 @@ public class DateConf { */ private String text; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DateConf dateConf = (DateConf) o; + return dateMode == dateConf.dateMode && + Objects.equals(startDate, dateConf.startDate) && + Objects.equals(endDate, dateConf.endDate) && + Objects.equals(unit, dateConf.unit) && + Objects.equals(period, dateConf.period); + } + + @Override + public int hashCode() { + return Objects.hash(dateMode, startDate, endDate, unit, period); + } + public enum DateMode { /** * date mode - * 1 - BETWEEN_CONTINUOUS, continuous static value, [startDate, endDate] - * 2 - LIST_DISCRETE, discrete static value, [dateList] - * 3 - RECENT_UNITS, dynamic time related to the actual available time of the element, [unit, period] - * 4 - AVAILABLE_TIME, dynamic time which guaranteed to query some data, [startDate, endDate] + * 1 - BETWEEN, continuous static value, [startDate, endDate] + * 2 - LIST, discrete static value, [dateList] + * 3 - RECENT, dynamic time related to the actual available time of the element, [unit, period] + * 4 - AVAILABLE, dynamic time which guaranteed to query some data, [startDate, endDate] */ - BETWEEN_CONTINUOUS, LIST_DISCRETE, RECENT_UNITS, AVAILABLE_TIME + BETWEEN, LIST, RECENT, AVAILABLE } @Override diff --git a/common/src/main/java/com/tencent/supersonic/common/pojo/enums/RatioOverType.java b/common/src/main/java/com/tencent/supersonic/common/pojo/enums/RatioOverType.java index 2b58b7062..5083aaebe 100644 --- a/common/src/main/java/com/tencent/supersonic/common/pojo/enums/RatioOverType.java +++ b/common/src/main/java/com/tencent/supersonic/common/pojo/enums/RatioOverType.java @@ -2,9 +2,9 @@ package com.tencent.supersonic.common.pojo.enums; public enum RatioOverType { DAY_ON_DAY("日环比"), - WEEK_ON_DAY("周同比"), + WEEK_ON_DAY("周环比"), WEEK_ON_WEEK("周环比"), - MONTH_ON_WEEK("月同比"), + MONTH_ON_WEEK("月环比"), MONTH_ON_MONTH("月环比"), YEAR_ON_MONTH("年同比"), YEAR_ON_YEAR("年环比"); diff --git a/docs/images/supersonic_components.png b/docs/images/supersonic_components.png index c5cfad5aa116d6d575d01f4c6b343ad14605e201..ef528d99bf50600fd8e2349d179d19172b7ff981 100644 GIT binary patch literal 194643 zcmeFZXH-;aw>65|N(%}Y0LdyU3KCS3WI;vA2uczVP=bU4$rM2aL`6U)i9{ty&N)iX zQ8H9fAi1EB3{}8g8~g3<@4MsN`{&*>z8{A%-WNwxwfD2vv%*|+&1b(+QIb7M!ALotyW_$jIS;hot`85C27>By@s|>^a%pn>W;*^zb7-==-%8Nj#QqQ(}U@_$VzU zIW7L{^SDklJHDN~Al($W+-mYjg`$I@pF{~T~$AP)8wRecW2Gf68O z%WE@HE}z>SA%`1~#gK1$Y#u`XMfNtVpC0)gBYTX9+?;IXqM<(W`#I|^JSFmvj@Z|3 z`;dRQoXp}yeoFUV_LPQ9QjQq{`UX>i>UmuFjAtSqoZ^GejW;2Za>$Xrkyvt z+%CEMGf+8%N4NOdL=&of)42N8Q6DOnV!N4p({v(J~98tHmwbj+ty??*Q zpGB08TbtjVw2_^i{qf_+PVOq|`1ttq+!VVL+6)?gHU@_B>a#i=cL*l1fMq)OKHludB$RJ4 zaMh$O;_chFl1v*36gX7>-TwRi2K|;hN?=Or^I*zpo`vBeH7%{{jtj$CM)f!45}B!wxT=8!5Io?@mxu zOHjDP#no&ySkGSCnQPXakHYGysRg#f<))7zkE5)hsMtE$8eZ?mq@k_-IWZ?d&8g(q zcs*($`_>Y;iugz5rj6f{k_NJ$G;jPGFA-kme%(lj6m?t*0}r~Tgxv0rAHBT`{aHq& zjb8zIpHTHxI?7RE&IC-EBLUMo1@3wB8n~I1MoLP`P>xA^&?U8~N0T}#+S($PgSp$p z^}fEoshmH+aYNv4G4cXJLeb*rwLoQwnQW6n`^ySOxGHaW%mxk!u9Muzr9z8s#_I~O z?m7=1tkr~W(RrD9HM;I@ulMxyY(9j&9Xp5Yfe74x1a9bc_(bvD-#4_M{Q>3|26Ddiyy?lwO(sX+W@nI_(I>&ovO%%bugI1F6mLwU+vR-a0s;U3?Rd$5+g zdi4rSr5qug#U#AE^uhPU7+D#t<6;G=nOYEpW7TL5h7MOH`_&+Ai? zl4c+RY$sc0CTUqIs?JI7l9rQ`lP4!9Nq4}}&Yl4aJC)AP&2hmGCugy z0U9MKkN{8>+xf-CB9f>6UP3Ia8;t6Pxn4Hx3xN#qLIM^5jEbkz^SlW&*o8TKli}>92oY)o?-QbP?(1~ z#){6)&a4>@8nm>uB@Xk&=yj`qe!OhF756NkwIW#;x ze3>0~^Q{;1Bv~cy+gr)^Q#d&}m)_?Q4+lOhu!@{2+D9g(dScHXz#Rv2%^+WC31uuJ zfoU~d*wiG~Zu|Ex;2D{isM#dMoR@xfb#;k7omXmVZiZcSTNu7h%Zj+^8zdB};^jjj z5&wuZ!a^42D+}h*(5rA4j~0heP~QuMFZuZn@c7`DGx_$cRW%nBK4p6B?tpu{u7Mp@ zk%VaXVR@>(JyNtlw7y*5eRCquyl=_*02x{BZP=-fTeGvXkjXIOb7Db3LGPG^7COGj zfE_kVA@U8`k;p&W+S&>kL$lD@KuE#8xv8mqXKN0wbUvCN7UH$Hki_zGcWk*^-~~kp z>gK>c4h{w5HT*z*02|7}qUE3P4^2)_*MmKUU=g~s#FTKw0pH&mF4Xtq$4^`EmA8R^ zCoOPuBxzB_v&zZIfq5#nX1SD~feE8-BJ1v9WMrfQ6GGDCC?6G4c!Vi+hB8v*NYImz z2Md}a7L0mcTwFX-;s}BM>u~xe_oYi=*KH>|V`YO^4nhG(46yk4uYB7TcUSq`=24Ug zL}|4TwGfikz)Ei;kUf7>gkE2OI+Lgrsr}$V$W(TKBKVS1iB^s=%k^h9BH-84ANFd% zJxj}Zs6XIFdfM892jku4wOgXZi47mvdpGx!Nhu@s%&7|YAJ%lmV~4b~v{bLL_$4mx z9Nx9%(-nQ!U$vXdQ{vS}0Z;HDK@`|GJnTBr6x@@dq5O&OOKNIrcj$Hq{Dg)ohh2RF zpXvgH%mbEiLEqx}#>0*hyFnjtKFtk)bCJ zoAgD-2MU3PmR1p99m}Eo`GFh`(lQu&sx6W@5vmW_boo0OS>^5(Bs=|o3fMYWAW;el z2ng`=|LE%jzb7tDG=ukDcUl~UH95jFJ!uXhloPF-eHUdmO!`lKmiFo3wxg1%s^k;H zpTl!vMG@e*=cA>NO(A{j6j*JO0IqGXbiX)2-kYIc;kvn`EV20*@W_)VPxkQFZH~dA z<03w13?7|ApcJjcDF^iYVBR7fq_~%lz}f?QK6o2SD1ZtmF#fFK#csqk*!{L>Ne^)G ziHQl=VVno0D(ds>j+XbIg_+fl#%kW+xF4ix(_(l^&79`=_8sE`oGXUpX`cHU8jiC+ zlAsI%=(B_U8?O)G<>F!zb+CBnvW(APhOFVWPJud!;UUprL=;Xduq#|pxTJ77nhTOc z1hSq04M3JMGSzSm1e(EPF(md!Td#5+UL{kG{XWVpLaCRpQOIbb76^@d8}}VVU?&KxTVEoi3n-k~Zhx}Ab*uK%6(b}-nwpxxO4cfyHh}F%f{}08LR@HQ zXb^^rY=4c_A_?t>^~K^!A=(T2n6u1niP7$iRNTxlW7M#L+sFp)B-ho|HHY$MKr$H2 zHG^F2{E`;U<|DoBW37VgsM_aFoG+l;h2R(BK!V>6Ff;gyR=MjYU>76jVKJfkOJn;U zsH$b+Lvpv5J-^|_>#l!qec+q+TcTRH?YVC+0 z^1*g93kxyy8h$F;L&9dP2H-SnX=#Cx_mc|TxyxGFKLLJJAz4ZSF)MXSQPCz8U`f(q zHT;3{pzHF*mE9=m0qott3u(F~YY>pE8-VFXULe>0GF*mUAN}r$;Fp1eu560JXTT$o z5^6pA{AC3h*<#I}A*8_XAejTKsDJyGg3DD@MROBM_TXG!hRzzSRCx~}vO{%&=MR(g zD8E%s%^vX;QXuRdtGJ7L9<0#m9LrlcpemvKKeN zI?8*c`1@Z)03H~Cidf&zfS`~NFeH*wz|%Ivpro)OaJljTzn~!8!45X)?WRPw^=#cc zh8*)g9&`77WHG+T>YqUjf&Iex3|_XZ^KxFg1Wu|T95zdWjng;|bu{;$)2ks^Pmja& zNr*=12t;khZXyUG9ZF@r43bovp#KTEtaWyI{w9POCEew300i3wz(P(4cysUT=*WcT z44xPYx>)H?RqsFsrI{#k^vO*qNa+X++z5btz@?Gi@>!ty9##oD2@!B9YnSS~ZS6?X z*bDL9PPh;(Ka5~>TnP^#4tzX3+7aqr&fZRp!+)Zr=ef+y-FiS?L`FCZAOJPTbx8u` za}xxmkY*uPrrM)JHzgjxUgFOAs6|G*!S~gW@4NTFaeNq8s) zk#b16O&J^<M3znTot4Ej8<67t;td8ib(5-H&_t_P{eLsgC=n0(_t zQ}5;=MKL`PAH*ZLxg)a2cXvzK~mDFq}53AYAwBfP;l^s+4;yb5&i!+cNiW< zY6q`QekxQ|FC|(fGvO@U%B#SLf1E-)2|C6C6c+ko4WI@-R$Pv#v%Xq==_rSwWFOSY zam4%J=f8uscrL5XGF*5}50=w5G^nAkuYZbG4+7Bn8Z1PAuTwdR=5*X0_t>qdn%j27 zHiZwZ?ctE(cdjBEAKRa6vmIMoE~C$O8Cg9m*WQ0cj7v6xG{B>(NZdWQ zl1E(XBH}ty1$z+eBmxRzhLFG(qd+{V!xHg)*4lAjH^N9sfo=WT-4k`*Bjv)2VK3TO z*tP~oRoP#)hY5`TXguLe5d0h-9>5}=?b6eCqCS9uB-ejqx8POz164tn?z*FXetymb zx-Vw6OHXilHq~2IAqfcynOUiie=y1Dxfx4gyAze_+6;DsOB@xW_;)SKu^ULp$^QCeh-DcnIO?(!*8gSRynk-DT>XBq-&-q8sn*i3 z*ZHv&8B@`2v%^I>o1cj5l1_wqX&*<7D?4eqD?V96&qad8X>21)y1fj|sn5!vIUhAX zx9U-`R9OJ~UA#w2sEYabJJzqImF@hJw9NT|U+to7*M(+-Mbmn*atAJ+Y`MF#+VZ@7 zsx7MVP(l`=mXbaSUF;#4Oha54$SGNu+%1@w$kF&z4epimuj|^m;B|l|F)nx8SuN=5 zWDj*MH{HyR&oNQ*Fc|GN%N1FQrT(b9GfPsQ>cY99x3^<)gw|*wj(2W^d4pZjkVkET z$)T0&Ln51gS%jf#J&&nYp)Mhc49qSGD<7H+D6d*#ypFFdK-PU@)5JH#W3x75;F_Kb zuE?&T1Vt^s4u#t8%AQ+Huk<@k`On)BQ+O+^H@o5=cVry&2<6@ubC}+4-%YDP3(YU$ z$O(15ju=LlnR_YHG_nEAN^^pxn|1GV1?tLYxbTlxlQY)mhnAP~%mIv&uMvC|J@uT{ zs=SwHSfzdL>wC~vPb^l?4;6G%fvfG+4Y9XM0d7;RVYCIXw64cBgu0;6Itk>fLj@L? zlLZw5D|z4^k2^eu%iTn|_t|W%B=rZ?mhWy6J$QAO2B7wS>|Cek)jlP9dCSgy@T6iG z|CzOcB6eOKAR+7+{(iK{l}k40#(MUT$0|~W%jV{Vuop|a`q%Tm^U@#MnIr6uHS%VwsXksP%Pan!!ERIUzEUrcr-b}0@}GIjS0*}Wc8!+NYMiXa=J zY5&U*a~M(z>)#iO&d{A(&Zvm*eCl)*y^Zyy=N>Le%uYzSFd)kX9q?U~Y=i9pmeNBj z+ZuS+i6sxi3H~+RUlZBACA$I8s_wYCv*|dMUHRE_i*ib10b@^GOLGM$Ui`U7j@(MD zFaN$~sS`bm74>~Unc=ZBUx9HoYWV8C-2J)qV7QQ_#n7&S9<-%g_%nWP-Gj%%nnp^Z z+>%j4+b8v8^qESncS=uG%%1=p@^Wv@XZ_r;&gQ0=;qpG8(&OZ;U?21C&8cWv_mvw% zDgth9VoTM_l99eLAv~L@meD@W^MggPnLhk$Wlq1Ohi!Fwgsg&G#lFs`clF6uV@10! zghN#S6>|8Y5{^TW2cl=9g$j7Nb21a3Ibw##nkJgDse0F+)-0Bn?O+5oj%Jud&oV6! zpenS21y{G~cg231W6Q<8#pALdQ8{`<%Zftv*n%Yaj&)VF0<&u>TCnJnte-&@bs0*C znAKheu>l0hKV1q%9W#Ez3X`fNt6Qudyl$F?P*ZQlxhnL2VTP)+O;;f=+?%$2v|C>I ztNUPdF6O(;n=Dj@2T2*z`-QIz5?8w3>gwV;wlQIS$+vx?46S;u-MvYB#7YFR}Hj!jvK&aT+!P-}Ffb}rooU*e_Z6;FW`KQijEMQm zQyx!EO--$=${pq*;Lse(jM4T)m)a!@4W{1RPYxSSKYdCZ=Szu0+1PL)l+|G2PVI~K z8Y&b=v%P0gj!8mweNZSKQ??wc?@py?pO<7sp+e^@Gdl|LcLyX=XazHzkE%$ch zr#z_+fqF9Juz;P4-gen#A3C-6un4UoLINw94PZCq`q=69ka`BWR=!?B0ng;m&pk?` zZ{UIpwj}jochN(=t(!c+4D31ZhWyi~PbG90?_%{lP)u*~-Bx#|c6lE1@zR+z{J@qG z+jL}x{G0R;FAxtdS^mylov0l7Bi}>NVSLMepwj1!8=)ZR&BmgqrSlMUt+0`_(-rnt1}cBmvT$+Ps^d=O;1Pc(-NOW#tr=Q&E?66-I<3H zm$U4fC#f-LVv>7Vb{2A5ur0#>v03l#OslfdZrL#tF(|E%rGQleURCrtSm9QOySFD> zqHLg#aRt7%BM-@8kqKYEe3>1qh2md9bXX-RPqsuz*aJ2Z%|{_*@;2(e3tN{TQ=f8_ zaJdn&*N>^!r|-o!v2E8b-O}v!8Z^ZEWzU{Ku`_6Y}pgn=eK7PZK1n!mLww^y}Jj({?DD4fs6gOPVQnhR-UpG0J)Ob5PW-4!g-nt zfvm?7Hy;c1DTm)=r;)Vq-~T7> z)&xlIoRZ82vTdSu9;Q!oP<#wVUms&%GA1K4RRn;o;%aDjYC_y40(w0AiXs>>Vji9| zZi`@$%!c90&(?X-XFu;S3j;r1%0Nb@jr5@>qd$?|b`D@Qf%N?A5kh{I7Tqb`oDBDt znTBRXMn(oIFVI(Y)!0HCBMbnZzm}vx7F#MCkOOR@Qzm?{J>2&O(Eia9ZWKO?ZK8yq ztt_8`@E0QiGc?%>Tm2cDftZb&Klqx^E5guV{d$z4TM{Bf?+NTtt;{EJv;$`Ni-x{i z9^Yb^Nt7ATmyo7o1OQw~{dzNF5VSsR6Xmh9S@q!++fx47U#&vens)Q;tyO1RB7krP za)0LQS5b$^o(m!oSIhD0xhf{sq{v}jTdWJH33w=l&-|BI5pH*RaxLE8kDgaALg)aQ z6w}`JpOC4%Na#!gc0M%2!*-@CJ~JNLN8m+ab#BY8LeX&R@1EoV%-5fNe|fMkL%*Gz z0|<@+;P%3%=V2j@ zs86*%jM3um21;y*XU!XsoTFqsDVyk#r*3?y(Tv`U?R0qwJdsd&SYmf6l1A1fQs7A{2CUdMWX z8DL^_N+Qzw9kpwH3rw5=8nQ?iP!b5MOP4MkwxznpcJ=X{4b!T_19|2eK-pkQ@~lUZp^%9EQz&Ts z)ma`Zow7iZWF#kZ+Wo0vA5nQg65*d-nG29ayt?%^?1Ap4=I{Wj++$;bIyNC;l1Qik zB9x0%?Cp5C;usA5uG`P5f2pqZrDt<`TXl=IfG||RYlY$>jh!ss-WU&X*703D1D(A6 zaG}jF4T%<$&abz^1ubVsab7sai+Bg%_#t<$>$Kh9xc<&~KR%Hg$xSY8Llcx(+Ic40d_f{>tKLVyb8);fVr zCIv>Y5)heY=ElGjd$%Qh{`|Qg7aJhaq%9iKE6_I9$=A~^LBtQ3MEkJ}a`N(`m|EgF zxa|t9anfSfExmI-91W-9H17m8a`DIGj(>W-7#O@k7fBE9~@)2Vt5nd zy6Z0NrKww-)~D|weyAgC!&?!^{Oa@c5~=vmEtnzrOFGg_A~lypMOWR+1XbY3}BFKCh+PduG+`P3doSy+kQ=-Sxv!RW_CPb zTwUL*EV<2ZKCh|Jbm%B!kQKFT(1k>nQ^0D9EV|DH3O@VU@F^+J1YC50b)mrZHcnwu zR|h?pR?wAk6rwUwnB+~bchLf0sAtRg-Ne@tJcg0SvwtwkB-(B5MT#Bh3^slAx!}f0sKz;uAqrvNV!+Ed2#gnH%kpWb4GexVWvT zH6CRX9*Cr$Jub1e;oBW${m$~l=A-^h18*uq*}9(akmb6Xwl<@O(t){_RIMDt0vH-i zw31bNBHi0tWd*6PrLu`Gknon*&9(YPpZ@rEkdn?7d|YcO4^pAs#$cX#cW;L?5WKMF z0m#<^KqcE1>1R9^n1->5p4Bhdgt}h7{LCvGkmxAIVOYc>D#nNA6h^OE;01v~&NYMl znq>wA@UN+FG(Qq>+8H64SMBs*)(e33It=*+|}7)5C;B(KeBSuZD3|O z$PfP3e^P9H*cSL-BHXJA?VVr}<$S@??>r?%`S#jSt^gxsTX?txrO3}|kGQ$H6@AEI zZuqvy@;f=3D~*!+#^dU#a2_fDYlr(3E>CoRw|O!q(w!VvmL^=cpE><`i7+rKRi?w-xac5X3+oRctD^IT%A7j7W z0+<{$9ylL6Y8+$R(wL;WjtyQg)LIapyEmBk{B7Si$I;s+EYJ8uu>P#Y6)6fO{msMDe|L()=$9F}M$IRqPX*m{4rmK}aNy!Jz=JQxrSWPS8tXemLOm6bp05?5M>Kmb z3`5`4Z&nZiKa$iS)a5tfj`TVlil2k>4gpel$I=z=A7}i3)K`?K#*gQ-WDLq2f-bx%)0R8>*#1D3 zcTiN>OPCwfQ29`^DTTunCA0_oXP@O(a2>c-SpLqJ%W8;RB1hyDZn;(N!ygdhZ&|AB zhcHWP3R`#MWo@8D7M5wq+l5E|v<~TN;?~M{UzzTFyy8*1(j|{8;8l=tmBI;4!!RZ* zsZ9Iq4DSX=i2#7bu>4gO-UE(s1_4({c_)+ujc?z=@6MeAm*BNhHi}e@y`AS~HMAX4 z$-m|d3ZI-_{`Bq1a8!0LvGHjKq=9@-Sr~0_j&;{jWZSv_zX22{E%Wc6 z1fOYg%&hTY-z&`&n3sK+1H}uCTeS zs6tPPNt)mav^ek1Sek$hRfrT3U|Q9>Jl>8`}*xuY~qbBBD6WT7yt`PyNkOafmO+O@5!?fIQ zsN2;V=ID*DvGG|1Ku+0US4(0?;)DJDeZFTJvyfBjy7KZ0_^C0SY?ZYM#A>k&%@T2MLE#I+{KYrmXW=cXsLmd~N$P~XP z;pW6@IKHet)k++`(OU$ywLeEg{>f~Akh^|1O!gkDaG5=idN6cnotY<}+}%KpCOx|U znr?G+PlJ-ot~%){4vD29?TnikP{aUy&sF~2Cr}-q5EoJYJQCOJbj_?X^gPX{fktk*1pPGYt8gniDE!JzRd-)o{^l^NRKOsWY(dz2i zP(3DtDsMHG9ienXsOF9j7}x`Q(-qlF`d+{oIhX9#P=?68fk0{)-m`{x5#$XYjr8VQ z4jC3`TCzuU%9b=`}DQqmND!rtBByUo?H8*+Q>_oJsL+E{js(- zgi2IAXtv#(T57@B8ju~wtJ`@UzyV}NDET5#2cJBdfKWDW`Sd};B^_D-=*ZrPcSqRm z?rb+o>{J5Y8>@1bG|LMd$_U&zPlQ$mKLj61lPo9N5sB%B>e& zPnmv|yoC%??+3Wk!d*QrzLNx)kOB?b$~R2tyh|w`<#xM2xXpYFK`jXH93mS;<~&fH zyrDWNJp&ob2Hn8f0R>X{wuIZ(<950dXjI?=lb{>{>q!#vIW|7jL21(pwgvpo?`{XM zvWUhB&|FPLnBv>WLSG{G~TtR}=sU0ycS~++vV!hD8{5q5sEs>9&=Taf$`dfoPsNQ;Mpm zQ19hu5-ATAQUl=@o_t`~!KDP_gU&AhIY8usi+jcrAzGKQ>-;aWa5K zcr6s@7k4fpv|JgH9DeCBFZt*nM?&0z6*L>edKwz^k_iD4TNfEV$fsD=U}Nm|5u%T0 z1cE!RiIpjA`Rt#meIH_PMKD8=Ns<5VW)sdpHyen@R!ElL+RFW=uq8ws_&m%a>QHm_ zpvOgd4o{>*@6F$Q1Zs_z5Bh*;Qw0P7NEJrI5V~gGjMkfq?$)dMnb@8kmCq_Z1+Rmx z$*EaML?yN=5I2lTfEMalfsY%Al=;)zhq6F~a2{PIY1$D}wdNn%@hbK;`gMDuJpHT$ z-)rUgTgSVOGn>s1u8cQ`W$s9Q2d&eXD(OqE*q!2bm^~#DEx1a}@XUQ#34LABUB8;9 zFK&N~@kCvv=&IhFP01KrgVa$o&vJRX9JFGjR>hRpacmvY$Iaiw0mec9VfZ~sEzz+3 zmM*H9(}OH za?3SnWk4c>AFK6+ud)@G%sl1;p>hA9gYMJ&_wQRoLf_f@*<~VFgJPsgCy`Q}SFiNc zdI#nN4wReSXN!{PL9qgU6%7(W0J*^rE-LleCj?rcpR=F=KTsY37iQ{H0XjF1nnGS2 zlq?9mb_xZtqK_?80Z36UEG$E2ut5)<&rSMNa`Y76X^GA`gQ$4Ac-uP~_o!J!axD&Iy z3_@$$*F<4$(aPU{4HwNXK;3n4)<+)yn)ieD~vN{^dbNC&72 zkeTIZ(*<%U`@12vz5oRP#cH=Yg1@J~zaLU`Riz`wnZqjjiqY+1gD++PaiHO8i*gK2 zKdHoaAw)tyy-cPYTb%q%`~2zc3#TK>9^d3?*@YhJbtGz}_ohysjF&@`p8*!92 zAVXqjO8gT3Wj$SnJcQX=ZbR1})W$^+UPN+#kuA=-|> z3?09X3+QoOFvHq~`+rP1ozJ@Ztlx47TjF!9dJYsw z#h#BVPSeq_L8Gx95=c|vCI78VmTEs4%Wq0e`t(NtDjzpKWdlt!Vfh9ex-P7yV!&f6 z(s8T~#NJQLP(i(6irtOGG`2t2D!1Zh?$uMBEd>Ent<^x&_gw#{#Zmkews*`grGldG z1w${?x@h!5k#sb^QG14S6k4%(MHYF_W=*9p>FMbNl&_{Ih&q;DBt3-s+MuYL{At-Pb{@d!j|HpE3-b&g?&s*7b! z(*Yp3D96_D`wYWY%wB+~*9QGCne;DHSAE z1qVv_@VU(nq6uXh{r#|we6<)q+P*XYhR(Z_rhmvMzG=D=^TeC-2( zWd?+FE8JZ%Q?)$Ga5b)jTk(`p>o8IlxUR$^4TMVGJeH{O-~rudmpmob*p0+#1C9ah zzG*0nGvj?-e?aej-;esCx)U^|!h&{f;U0k!`^|wVF;jE(&8@;$Z$SGqE&+K!zyFiO zhs*L?_W;bk8|gAhI*4F9i`f`EJOnvyi6xBntR#WF8G4f2+@0~{e!Vod#^8|E zen5U#ryYT)1wpn0KBSlJPC>MYJtxCT$)akVOVbU21Qv1{2^rdXoD4tYPJNWPV1=|O zmvr}gfNMWi0etR!o*Q}+djE?kD_K(cxrco49Y7UTRNdDQA#LuZ9qsrR_7}EaLwX5s z<9k@0m1JoXtiDlunb~MV`DTRWRydLGJR_VYVI=B?4ObQp6~yi8IK{E z@d)ry?}*CLbiqY#L2C4L6tL(A>g+6qHo{wHIGP>5~wQNB!#6L7n&9=?5e+ zCSkPV0DW5_49;iLWJ$6cH>+?pR(tK{jLhO7@gYUm{eH97w~MyJ7zq2^OHVd;%JoES_DgogVq< z+R*Eu6~oi<6m5upq9Vj(TL(#O@osC6K_lm^0Sddn5Z^68d?t6I>w9kOAk=LCd``QB z@6fnmBXmLey7yfmk*;K^eh2WB1LrxK$V1p)D~nMoMN35H>6N=)PtWCUkh>q(o+C#u zpU@>o32IMg+h!RY!bJ5aYRpN7C}Y2R=4DZ@)G?t>9!ls@?Iy4BJ9Xu&WxpHDhx_!< z+-oyE#ZI-Lw;WsZkLIP{B}tOrTTv4}88_jMn8X_~F)?*F6pl`J#%9SBH}ID|q5uI# zclN1+GnW+UO$*%a07tP7XE!4KTd9_ztG}|b(3Aa6Tt}5Dy=SGE^G+XS;&&V1*w{1+ z$KlwGneKR@Cea{n?n5=N>QHSyj{OeTN&2`xkF6i&z;_qun!?1oP4i^oX^A&kr*(f~ zkkH=Q@{zwnw>aOkHRR8~vS&=ap3VavBV^Bg5QQboPo$Qd46)Gd(mG)D_IbTrq0R+s zvJ}+g%CzZkE{lZg2DC;>ks`r7M8)RP@}(v& zXnK(2Baqmfe4)NlLn+Otd&h9?!oc=6(zprcxqW_kE6`BG~TcU$~UKJ&G9C1WJkZpOUWYQ^Aj z;}S5pMr!2BUQEzdX9q|MK5lr=7e`aw`soV0mGbzgq#aBu;tkgiB?Kcv_s~;s)&?dgC@<1vEIzG?ln9-!fsJD_P)j1tSF`g_`;2g~E z+TBIvd&NOvq@0=WaQ;odu(i)x+#&|Hs$^-jo6`KP27#w=FJj=gh$|p`Wc4 zce`=OhFC&=)vJFo1Bqqmwv!nN5@bTE0K$bD!O))I5P|hRY^RcZR^nI+92A)`Y-|A> zHOMogcpvn~`sHFH$NPNhj%OV>bYw$~I#Et$dF9R1bsf3G^%O^_`CV3~5rnN%MWQs4 zgeg!}2;>MMIK?2m_QGS7t&IfoSfgkK;r?7*NR|=FABD@{_A4C{mnswJL`TtkyaF81 zm;`!rRh>=}nZbEWI#Ih(m{aF?&B3!#9|^@ePelSxj8SH#qFb|uC>Q1{?Sy7n*OU>` z2m$T-KH7aNskD@|UbZ_4>=c|P((1o2Ol@ZWvg&Morg*xxV)v}i6)5$E1^EGrDapwc zs7}E?z*$ptyn3!cGFiu6`LWQm^eMHfQq#IW+#Sooy$fxcL%26bLpB7`%5-Sl-@qv;3iF}yP;%(H zTF7@VxZZ<=?)Ios90CwV$$8w&5fa(F%+kGsQ7f0MS%TO=gBkUER_lt;Z5$wwkPToR zfiodMS>#)dxJ-9^!Jp9vwAl;;=|Y%*0-U@)qSpb(WME(lvlgThuDFhV9zaGxgLzzd z4;Z>ZPPPmWba>0>1McTl@Mq2lq&on{*^ST}yTHc`_OslKkGvR>4#EFw6NK^91`-@f z5e?7kV8Py-csO0sTEgZ7Fb2BnIuAiX!Me&U2%&*`7H=)68^RGSe2sj&d#d| zj8hoWzagzUSOgb~K%v1iBwJYHm!z^`LC%x1{<}x|bW%}#aB3uO+>@3<+{esfFn0z? zILp|bVr#4FU^<>;*c7ihNh@Uyt^&7Q-5Z6hRRT1|w3Br1`>F@52ss-vIFi>j=Cu<_ zNK~*+-ms+RZQ<1812T0`|6TLn$jnALX#IKH12wgrMIjhF0*7Sw<`w>mi?Rwdq!GaU z5SJ%w5Dt)3-R^9=PD3wRPr+Ji4ru(BSAi}Q6`2_0KM?PE8w|Qm&e#@V*{2ZvEU`=LMCArupaF~qaRuXGXt>@C<#-ox6Vi4G#d9m0jM6&0|- zu6|xjuaebqG$rM1$Cgb?2_LF%aXDC2=fUw7vMjbzX zsO#xP>AO;QL7st+PmV|D!br3Lu% zR%9J}=c#Hr0u*Y>?SjD>Vb3S~^vyjF9TkByTN2r5z{yJ2y2)V(WeVFOkv(K;vBzH+ zBxjUwJ;e;qBjquzeLbUA3ZTc5JLs*EQ+^jEv`o9?@$!Ae);NTe0sdmGg1vE<4zJdEo)TK`@0xS}o`a zz%FRq*-Ix6%wQ{ahfoz|#;u>N+^hD*-1!Z670|JznTW}*!aj~9C`IGFJ4xz4XwO_8*qMJ6AaZ+p16 zNn=NYMY%x4M^!u&S@hfgnK&IEz!F&O3Pf#xXA&Xxg;WsXi%wfPPX6x5QQwAPK~jz# z|FfVyg~9)BA*-LzxYV;rmwtJ>#7ccnS2wZ(&gFD4j72(Q7QK)mdhrL4O8|c2!0_6C z%^b>HCT)X9w9%kSmtXCPt(SCv3MtD^<1=Q`>nbuEZes3;)t~poN zq5cP@;Vt~@N4Gp(EHzbcsAOrV<0%Xe1RJZ<+&FN1{QimBQO7EG=Js+EM~4>!ks}kk zXv~yEQN((S#TgTm9R}ll7D1f!q%mYU@WVAKJ8CDzj#!C|%t9Szr?trP;ivaIb=A!C z+rJfy{pt7g7JZS&g`ZSUHm~k|ey8Dq$;dKwvjNhN~H^pCyJe4fVj{a{7m59Cv1 zm7h1h2(xtj33h=W9gSg1cN@fBus^}SVr@iX_TDf-M&=Ns`L72WmmUB6fj~I=&tK-o z$$$Ry7tgT&@-Nr_`HSMB(X)Tqqrfr<4$olBvFFhdNNy|`aqYIx4ztLwJ+^;?P2HEWVDYAOz+1GNnX^>@4=hD z9yum8(CE*Ta1*V>>9sS6ky{#%$?9*h!!dylD&5%o zhUQ@$hzDMr`*~_VtWu@-Qt{aX!aR)E!4Dm#8PksIG>ai&E~Uj$PXnv;&Q{v9L}vq- zB3I^rOi^j`Kc@H}ulRor5DheF67R^#X+wVkoyRO?2tB*S+lQi zb6Xo6zVnz?xXAR()^>b+Jof8XX&D(%`u4@d#8g&Rj*gCk0+>fT*AyDdH*em+;cN29 zLuAJPk2xR(kpf%6S!f`=NC#CXakwiWmG$!S0vz69`GUQvq2W(z>h-m?gbIq=8yg#N zFdYvB+QEYd?R1`CKpv355E>Doz#+xJ{$Hel#n-QpI+t-MU9SvL9KebCoqH#J6BDNZ zz$`B>&p3QnjY~`nU={yd{>ylIbrs$?adE_Sz+v{srLC)nzsAPS{BhCfzsf>U*E*1z zoR|Fa<;$Z?f_4NEUcm*zVxW3-m5>;F)k??$~gA?)lMm#GTLz@Bgv5g1P%4v-EW_}_9c?)rhf zIyBeV-X4A3_JYS&{M6VONa7z9GiB-&6%~Q-wcuCfu`Iyky>eNXZ z+W4FSOecs`6GgeuG{l!zk%A_tQbKjXt!s_)^Ah9wAQRy-l|C2Br_Vg2qBY!O8h=(O zMuke{uX^^ps}bI*pjTGBSTuX`*s&Yo3ZS(0D)ff;kVR};_oix%QO_`wuYy=SRB{63 zAc390!%*

4tV!i4orh;@4V}T+1QB!F~SzQ1<>~axSf0(_1NYS>lwXAFhv@P}8J;});aB*u3JRQEN zvGYRh=b@8Lr2lMg&>%?8d0oGXfW}!lw>k)p5BciA+iq~QHRTSh#3%{3;x~`rb!xe> zG=};yP)D8Gq!4)5$9Vks@r|7PKX`dFBzHDE7ijVI5X{rz$;l9#FJJC2?#l+KcM%E2 zWX%bot14o)qfdKXr-SLzB+Q7nhT6+raq-5~5}KY4Cue8GRWQcIN9)VSxG!6;A6CEQ z`xk^zc?v`V95`$N;hXDB*ZIN6%%aNliG8VB*F;5$6o&eu0d6NwoT&9F264}z>YrJ> z2%Zo#i9x`rxeWv@$Wo@-wO^#9aLui(ud@MZ4ryn2WW>zJ1|Rrl;S~NROgG&`{ib`< z!ZR{ZQc{&Lk0q|mRJ$9P`q;pvI#HBI9)?Vc`p5Pe>c5A_X(BP=ByWgWb~qC}1B_d# zCEWx=`FX*^ak7?X1%chum5-d8U%RP3`SKqK67K-2TM_%&#ej3hpd89=ECj~jtu0LW zO-9;}5u375rl#K(KMG6Z<)uAp%Ae2x9I3KBvgiZ^g8Ufx9#sO-n66);GzqUv zZ~$tGm6g@*WgK`c2%FA!f9C-aUr>4EN63pZK2#G^Q@$sFEozoXdFkod+UNmp&D+Lp zZUs{mOd*J6x(}<=H9$6aVr)5R^X=IL=a|`n@g5T??MZ>6u)gG@S@yHNClWK4Q(oT> zzY7O7)V2N-n$H=*e4DbzyW{2Q2Mw5us;hyVw3Ad;Qex%mi2j)wWP}Bt=a)d%M26k9 zYil5GV-d1q_P7nyNCL~~G=ij9tA6LO!uRiKc7l#Bt*>h+S?q4jWjudQCZd=PuelOA zVWH>lULG1s@5Z~z4Ns=j9U2y9_Kcl}hbKg*j|{&D<0jRcinvlgw3`xR z@pVS#7fwUwg?dqP6(#W)ptHqg52pZAmyOs$@4ltrMEu{XJ z#mVUD>A^c!HWH7YU|~@VKXS&=2}Ta|^B0Y1h|*dJz+C*}_mZBBl%e}-YrQ3(XlZJm ziZr_E;2;>8ih{?mqv|h(l;16M4-4$rG&4J}#&R5UzVF_}*^Byd%PkdRU|s_Za0T z-ZH85>1v?m&%Q89dhd*Z!tI#hOZ0~NL2v&y@m-j3ZYCa&C0>&aY+zwEG%_;talWUf zW@Kmx!)!^juDFhl4q(4o#GLrJxO~^NI-!Qc>#}m--8gWH2i~if{GMzVq1Ttk1V7gXw%r(SQB`S6ub}x zh8gG1o$H{=JO%xS7YE;7pTx~2Bor#C@42=xB0qI1kdLqi7DHH%T{j@sp|mT( zOQn7`d;pf584|^vI}M;=8klunoxLyeC60oEqAg0i#AS8Xd3o|6Ir*|%mhz4K40yAT z$>YZz*OQwX8wZN*xri|^wHk)O&;I@U&z?PNCj-oI3@WZ1bPv<0mWzSA29+(8KDR6g zcyaCQ$A?A#JSFK)f|2^bxCt){nqlC|QS{yJt}amju!$(%$CttD^$w!R@b*Tih>Q$g zH2Hu|qkqfQYuC8T9F1V^V+-#}Y-nhJS9@i$mqG!_sTTx=InQM=$tw9~!ssROyPR%$#;_SW+iJ4(#KM%d~%Qx8j4PpWAwbk;}x8AZoQc&Yxh zuBV8S9qU$cG*UlKb^LlNfVjka4=o0z&K9e!snw zYccTAs;w~zUKq8oumCgM4(y?C0Z6Gnd}I$DHH$bdFng3Af)@#ev|Zuh=MQS@lPzEJ z4HLF86k-P=Tk-Qz?LzA_ky>#utjQ%}%0PJ3S$-D(YtCf{4pAff7~R2f+Scpi{iqM$*FVEnpCyoB1O&qu z$c1Qw{C#*!!^EF?z;b*_o6@Ud`A|+gNaTp);0z$)Zi71$5>gxAC-g> zzk$jD+#GJl*^`k#6E-Rxk#Z4pqVNwC=7NnxMiWB>7t(GcT*8okx zDFrDu9|q*mPFUF80@DVNxXk=K3o4>QjZb|6aG=7Uq24{VKT%@D@3~*s_5D!-T+Iy) zkJ$R{vKjg-%o`gU?Q9>(BUt~c7MttF8z;MRVZ+E_wWi}nSRt%C$g-YRQTc5Zk5ext zXwHj9knY6R>HWn9niMqj4--P(a`{Zk+y{_2rP0 zzv&dD_s6u1da4SoJxP245sVYRApWc7#F9Y8=lXJZ)fT-zT_C(mttr~u+YlQU7l+6* zEY#LOga)T$KU>|BT-(m<*uGxZlYHMd@>4IM^Z^7*Bw{C!&Fpl&CRjb zfD+0gfChY8xwSIN``I_yxcGQlF0BjF($Ya0{}*HL9Zz-t|Bp8*G771ztO$_`Ayf*< zND?A5Br7B9IJ9Jw6|zT0_Q-Z*uk7U5k?q(V;~2lkOIO$Dy58^i?R)$Eb^UQ&*KyA4 z^?W|Y{c*qFAB+O0;qs#F!1&JQ`d58z+qXCBR*DJ>)4+HbZ16TIDp)}8DB!U4Wb=q; z6^z;=_;SZpu-SZPSwUEWZYu(d=@Y*WA+V)K2Nd{4J;j`fMMhjB<0 z+IOdHl-1`&6&Y$Ba`)jCFD9P$F%EyQP2h zVbr(csSobx+?*V2>IMAd6=3f`=?bDGG6J#4_~;5yMJ7f^-^+Vs^C>}*3v;oOq9QPO z*227vC|iA^9K*7g-uL~!PYMN_+%vsjW}uV^3=C9JbT#F|GhBH}iAr2a8rLO7gZ6G9 zj2Vs*=iF^f4pf@130+TcsS7NV3)nT_W7&Lr56s6~w{II58O=yg3S?7ofHza|uM_H7 zI z8R7d?=L3}>Y$fO_z6|En`RBo8m~uYw9$=Mdkg0*v8iM@_7HOb? z@h}4}0x8oNfyH4C9I-dvpQ``pjo#kcswNr-@~*s`95o7<75E<-j@k=NS=tw~Cp`VJ z1;BGg^w@n+#+1C6IfwraF2dygH!dJey2SP#5N=`#P0EB~4;wgb4zOpl7pA|uAxJ~` za00~(R+%;~n@}=JCIx>7O}4i|2((aga>us)-_~KE=Mxtfx4{+y0R#ryIA9b2VidCT zhAUWNcPm$oqKXY~qPWI<&mDc&7+el*j9g`@2q$N>bpvG=Y8-VABM|fX?suUFM{aDS zrVqYH$!c5SW-dPutHrY=aPIN4TxNqSd z$hpP~hMZZ+L~!cvsv5Zpy_RFLdU`AtI&gEOMM=)rVIWciiHVjD8&+6h)a+cZ@TsN+ zy0-vrE@33z3V53_aq?TFwwc)ZeZzdyX}8YPdSzxJ7Xe#73iO#I zI)QN38cd5T?eIxz>bA!xgJTlJ-K82RohRGEgDuBN>S&#am!;+IZMNEnEY4Oj} zG>Zbz?jM#8bO0@j=t3@7Tvc}$s%Tihs2zDzdVk_F~4MQ&?VFdKzfs<_0W?4)XJTG;UBXv692Fk)x-9(oi1vB#CAu z`BAoof`YHTs`eP_04NgQ*g^Rh?EQ!n-`vt-4n_l4L!4ekWk@^SKyvn(TR<3}h@`l- zu#e&4dAxy<*+_E55Y_Rb#}+`{s8KpW$kBeVL^$i|;c+}1fDB2|>+E~ye(|q!4+prS zqGC%u|=F6OH()cOZ-Ln#*Ye(&e`z z;e9B65D^|cc#yCWIJ7gnje&yv7 z1fI3!<;xQi*X$h~Kclh*gQE5lmw@%)rA7Dto&n{#^Jssp9lWt3x9;_y1S6Ag5YT9) zXSyubpU;XEpW!!*yB66U8j~_&OG$|1dHg4&efeVCDYbsc8ZcU_*+MJVNDp^0;30it z@XQ8$=H=CuxsrK9Lqkupeqe(O-gqAr#OC{%2619b^3?$(c!U#hH6si|Y7J%xy%u>j z8Z&=1MAC~m_01w;#l(VKbpkpigG&`--bFP=d)yFMN;GQ>6E z5gk`vULF*8qobqMb#=LtEWK<;NlD}PUL5)Q(^PRq#a2iG?%lK#6BD_tM1ez`^-&>N zOl^O?IhO{m;mLuiWj@h*u}^&b_;jWC#Qw8C;uZudS*=*sb_H_tXT@(~HoyX)vE%QC zKm3&DC7foc?c~sKEHuXuo%1C5@jmYEly0F?K4-F>7yBB%#XiKDU?3YwaQ0I)iezW5 z*M|Yr2t0a!L7KYa7cU&MY4R%etp=@!CFV> zECW$60`P66MeiZ(!K35j_^D1eiM5UyD8_7T@+!)WXnPECedR7L_}6V=u3$APm-rsQ zW9D2a$388{J=W9&VcPY?=U@P;8rl>g^1@T@{6pN#__!d*+u_A9fJFr43`D(x3+UNT8F}zy0APu-MGrIjnCZ(z1zc7I zijr2K%0u8Rb4nA-^+&_UX;|?CbkU_c#(DEu{APV<_8o^batLLg{>~|awuT&oi!qkJ zYDdK>M#(AQKA(^lcEzqiz5sn-+SSj>MOI^Q&~mLO0CYL+9dMMp8dJWzBPv>VjFc3H z4>a=;srs&1JD`q@sHDOx;t07G{$|!aQgkM&rq7~t{#w>O9H1W0EIF@UUEjI#tA5~< zkSX@7n+&dLkXAQpKaeNKo88eDkU{OR(e_@dNS}bLb?#vE$TawXXoyTj9U^&^_%M4O zTpzGWe39qCx*}nTlzqYx2%xd@uR}vmbtrPW9PI<9qfGfmHlId3IMhfb(&d7TMWp;Y zKq=H(v+%7oHas8Wk0v>FyL)>d8{^jKS==9~FNLE!efrMC5p1d~$C0h=?WwmXYB0x- zAJ0?#IX9QFm8s?ESZwvobo?+uhhZG7n&BH$M`$ZfzlGLh>Q8H;_7$Wm>`v~&W*~v0XScI=_w?L*+94i3K2W@-V6Q*; z{9tOfUieMhuE@74O$7PvLoh zIG2t(34?;trCfO4qQ)I;mi5?^?ljyF-E*PgSbu*C@&-j^AdZ2Lu&E; zLe0-8r6e#V0u|?ElqXgPhZ?CqDk2@{R7V6>-B#P@338w0Z-Z|JoAwo1YHDh4df|W; zo=Ppr6X=hp2{1paAI<W5%(5GqYfA6_ z_2{pwsf8{uk5?;5RktImK#Hz(jc&EgM0&!du&^+wLcOs1a=`68HmIjIfED84;9z4L zh5mlT?40@)JV?#ZU~*IAoNw97rrY|uj2hg<6Rs+Sx$0@#5?vtAw!Whqr8N4;%EH1z zoUIl^A-*01jMi$*4}{{dpzORC^Una#cCY1)tPW6SZG>C_np|C6^q0KeL!<1X9UT?b z5{`7_B0ShKR6&+*JOY&%kzh%91;eQbT-uqRd3brvfzD~;^$8B9`Na(|3qEZ|lU2na zQt=*e{PWt8&CbIO)MMC?Z4!Uy5s=2+UtiTx{Gh<;j6-cK8T7L0pvoOrxx!eKF`oSq z8qZ&L?LqiCQBIHMEv~7i4e+jorYVt2%hyaA|e8+z$J>5I!2T?1*8b?-H6471=e6g(p91?bhD zWWZ-Wd-i!rKlxtl0h7t^DV?C#tZ;c+gbgV>Y*DuQ0V`K&_vZfn`#}e_cX+^P+^o01m>ea6SV{$~lNSY;3oL zl?0dw@_~G@Dm#fcHWF)Vv)=535Ak)ADYwF*etzBG!M6-^-p)fyUfTUwJ7{(D?oiUx z%WaYsLjq@H#fEG2b5~Tv{RTD|%6G$<{4Z-e^(f8pt1Fr4Rr}=aPHR{`sKFL=Dz=Oy zcb2>8t8Jv)4X&NOiUWW=UT97v%1Noa$h=8B`(HUT%RAcT2jS%)23;~Y0a2TupMP_C z4u&OTu+|~1df*w#m;bEG({CEM!@t0(W^HXv z8$prhb&`sU4Q)M}Q9QJSfbLyrup642Kgn-`0arC?_6uw-(&Qn=-{4HPs4f?EEo==E zF4?hT)NXqs^%H#!m--A9-$0P8mf-KX31q)zz^~xdy0ze?hDH zgphxdpjG(%mBK|9US3~?lW*U>TWZP5x+ub<6YYPnS<;B4el_Ww{tzg4ytY4Cf~fB8 z+e7&CsZOMit-YBfwE8a*29yg8fBbAd?X84Sf4got#aV|LuWlwO|KLskoK^_`I7?^0 z*@-M|g$@e*9x*78=8Cb11dAP4=@EAP^|<~-O-X4Y54xkLm{^edk5@Bst(namAPbxAvZb8UICoZ~RQQH-7G=#G4dAH3(2I=g@-( zubfheHN4QEs=du|DwaOoGem=HUWvxp*|}2k@**~(8B<-ThHW>JlB$J@smEG%hf(k_ z&VQK<)JcKlq##Oma2ZN`Vqnm|aR47CzZtBroiEVA7k+P9ZwbX zg50k3ffwfTPiNu9<9_afyv8QxJ5Z}WEP@Z1(9^b;E(!<=r=Pz9=>bNDW3 zHIKsSDkqA8IRS*|(pa@@3wXc|mp#znb*C&o%BI#4x9FEvR;b5MkIl{Tw!m(b5y}5JuYz2nqA2JA z_V3?cv$-P#;e09N7vKhGa6;n%T-abzf?a7|teIAhYG2Jagu==%~KY>FJ$1=QF}DYoM(nVKPw2&d$aK-P-c7qBrKjH#amG z0~tO3rsB25YABZ8-XVHV{~XehlT0bSJpiYbY@U(z4^r;C3I$?S`&;hvm9ddGU|@aB zV45QiHQC0zn9)eX^+4^*Gny%WDLLVA0TU^=RlwAugfuwu2CWL;#(K11+vUH$~-X!q9V(4s> zr`9bmOHgOVgF2VJnMhp(kIV_pxbzmf8ULF3et&01T%GzTf}}j~g5xjVBIOLTk`r92 z7?+cE_V_tP$M2qKI`Izo-K{C3sE~;z5&wYgTgqjbt}qGCu>r>3b>*q4sSyJqT-y~E z79`2Wq1kV;5@$gXTBb2USYJnmJi9|kk)hCL#Dh18LgRO6i=AQP)5v@8{>7J(HVd)f zU*QU7rWWNi=agVK$npGGT0)Msi{&IBHYdiFK*sR-Vfq=1hu{|gf^ucJLujsjeezr!b8k6sV6x7jY_X_G+NoQwq z!uxwNKMDbl^Q5}VL?EXV^lstNySRJb9X+Gfybu# zzCh2=hgNze_LS9ZufRYM*iy9d?W-r=WkP6XTQ09XDTZL1E`_n-8&T{FFN zE4%u2&`7x)rY`2V`m1hC1ZO&09(#98A$#04ojOo?yogpy1h@}*DKrGk`qH*_6PL>8 zbBT`wl5%B*ck4oiocK(CX{Y{}4-%~*9_mEJ^uN3rrVRnPtp$KM{e=Kp+b7A5VoXRN z`3?>_J4z}k_t|$^6%5$|NU}4-!|8S%0H>$H(E4t`^zd!-)tZ``)AV|McVuKJ#|H|a z*MugAw6Dzzotv8rPPi}cCr0EIf#KK2ruB99&Ypcys)q^cw_$Ji@5>K&GDolalA@> zxhEaL(cZqz{1>c`;uF!nXo|f!c$$*(x?7i2j?5U=4uZK}EwTVmz%Kz7#_EL7)$`qZRax)xONK=^4fdnEW6+DE6JI# zosty&%WYf)*H)gi_7&|GIJOg?#JAPfmfHR~`rZN{=y z*N~4MW+Jg(aieIlwXN;I45KKtu42D&5wYu~To@X}M8X|$#By+M|9)#%;M@#%;xHsM zTrg{CGe?u`F84`&a9Ih)}67punXd77z*%-_HZ^z2|v7@(0`0Eyo1T`xFd^(R$G z2>hmj-II|y;*ygCQt9@ihB9(;33MF=Iu!@9wVClWe4zq~5X-u{f?Ig*_hY+@u4+%! zigQgq*)KXh64{u!9Ke2;MfSp_OZV-cp3@tGg4DB_gqqjL@F7yJhQ4C3Z6lk~XR;_( z7^7QiR8K!Jc1V3|M~tBl#yxJ_P}aoL|0!#hO}Z-r{luJi16X_`FO_ZJQ7v;(2i?5q zgr2U`YTG(3O&N_X3mMkCXD=Ccc6p%>*1EY%%B8!R?Be=;W(x}Z<|njmDfKnu8sh!z z)|S+%F;e}i;fgD~+Io!1T4lD8#X#lhqq8`RlZvHAxY@lhZ&bpdHP6p)T!*-q0Mmc~ zKdwj+GPOPQxQRmn*mX|+qkFs@yvF|i{&%pg4LB9xuIB`1J|h?GUeuF0^-Ou#uc;+f znmCKTn7#svRwkxs08!5zdls314e3Ad+8Em5f`T=umU?!P&NYr#h9 zPB}jyNhoifdFolBS)>9}N$ZV%&!W9-lBggdoMfOCSP+(>g+pE280l=i#^=lRbaS+G zMOr}t4#5bfbn{pY!);q85q1f|-(&EY6zFe&zItclZEE$JQ;h#XwNU$AO>+dHRE(n} zT!I1D`_AhIxNa$oQ*fkwr#LGF%9`D*$vP;3(Mm#vIC?hn{Wf z5G%~#WjRQzn=A^TQFc4bE_zNm5s)}2vN%YKZD*e?kp+MuYCG8!Rj1UL#_7=LyS!>y zFBMLvX%zUb^f~Xv>F`6NA02@*ysYSoG6$|g+tC;XWK!U%0n<=NM+&x~j>NRhHx$5H zQXZt{`=Y1)P!!LQsWPLRT@fQR=s5o~dxb(`hhjt61ML@uxjrZVjTwKNrU0Uy&OwB~ zYdtRKxRRK8==Y;OCECZrbRTeY1EWHC+yFixbFw$W2Dn4gEl!!#&Sa0E(=jf@E zan|zr9jX(AOStdYpF<}+U_#ankpjBy;tqrQe0W*7(fSCZMesDpGP=P#{vtqc2#Zx) z`cxuP!#@vae|aaN{$^`y%QJjtd){SN zx$tISPqw}rR>Eohc`vh0G-MTT^w&1w*C(nmkd>gDi?L?g*w_H7nN7}1?VQFZhK3!w zHy%6)%F=z2Oam~*{b^I%QciH!p&alOUnDvVoE;mrv)Z3b+F-K>4KJ77$+>gidH|4s z#;KQWVP)mY1RjI%BrhL^lWue1sWgZf8HLZfmdl>WKO;V{=}3){p_4BRCdE+{!NFfO% zvWUqlM5GsyKo4g&gK1RdtKJTE19INA$)RFDaM@^E(mgv+u@n z%cm%?#(Ii&z=I$J)1;JQ-RpHz+ON5M(3%R4el2~+xkh zCDsE4W$MZeU{3yp@_oJ%{K3I&teBxn%4d0Us&kPLt&Z-Br6LSvFrp9Cx|#L`pH<9$ z6e-hS=y&1CILpxR{%vBvV-Z)yY?K~UHXQ3XX}48yRM~Kq?uyg2_Xb?lkb*ZiQ&f;9 zo_%_7`IQv;Pf8dkBR*L$ZO^feK3)PgVh-i5DB?FSa zn-HjHFS82>ynyo$7RqP%^{SJV;;|mWqE#@zJ7>1IxClO3hJ;7?reO^h1-ar&JXnY5 z*=wYDELNjxaOAivR^qLjTcAblLD9VlkRtIOH1dIlnz}kVlOGlgjK4L3JJskSJFjVf zoaLy0j~g+hc>N#vet8 z0QaUEOAG4XA}Gn_O&@aO0d`F+DG`m#Y^|v=fXShi6_>snDbCuzrCZ3X_lhO5Zp~ZF zBof$@TwCw@xwpo}#&!k2=3k${O5#wh-(4>0?#^+#WQQ-6i;p|SvZw|AQ~1;u|6{L) z`%4|QK5=00z%^ZRh$fDN6nRTZ>I|tQFwW2r%v}?_!!H|=hrwoP+1>;TU>y>btDA%A zXXC3F>pS||h5%~T^1di_+Q?a=jpWzJH)YPX5$cac-^R897ltOFb7f@(7$P0Uw`CqU zDwbhGfG!ccOm_Op(>8dWSc?OZ+LLfaaYE?;Fxz~*g*kC!gR$`&NI88Tzi-j99SfJf zjQbMTiDSd7M2%>k8fX;lX!!Q+!Mcwgu<9izUMBuiErI>qF@&6zzw~EsT{!=u_rL%F z_4Vcct@GU0c6LlqPrx7O0nhgf8nAM*b%*)tRqT_k&%Z`Stj52cUfQRpr}s3P?c&7+ zV1z^Q+SuCK+SmxX6zG{xnq5k$&gO#|dEoo^XU9Eo(LGfQ041hxVpCVjcefR>!@HLq zqBZfoAV2V55E2y?g>{+G8QUU;MHdu`lAhrZU&*m>0WY{+*l@(k8PW<27G8iE(cKR` zr^OueZjN4n`AH@{J($m{Y?u?ZxE8Q5PhIG10pWjepu8baWk+~gc=fd3mG$PS48he0 zGePE^``N07K-Ot2SHdxbFfl*9`%hIJnLY;(m^kn*{_FME?caO-UU%=@;lCWr&Hkp~ zQhj4%9D4e^JTA2pxEe5Eg4vkQcBU|G>ZRe~VVe+FAaBSIUaTm(sItP1xTAnpKGews zVMVVcz^`ld=FaKT!--Y{3HE6!GZYcv6L|uad*I-~;PNW@x~{HOgmh!%iepFMd4UbO z5zY16uF(H1EH3Ku`3p1efYr!U)O8c2srjS%Gu86gi};9WPd%=yQsT((TK-K#c|2bk zFyl@WMiA$>cJp9pye?!w-?0r|f;J(yzZK;bsR45xylYb529ezqaLP{+b$n>7Ewukt|Z_{J}S*04)AN4*g5 zMerbOJ=_57LYeTn*4`r<^NVnEA}=6}AY*+hzB{7?y=;o@q1CbR@kTpFeLulWN(owl z^=Dc6shh_bcZUGUK}J8k7wvHs&i0aD4I5le+GZ|XM9xf!mk35m>&r9Hszf-L; zF*a7?8$6}u1x69B3imt!#-gPSf#Gu7_LQ>1K`&$%t_`v4cOUdTjn<`$P|+7NuY%K) z)U}bIY+{`y-m;oYklczXUNHbgV+*mYvAKB%;uW~mwWD8>A za}8@E;6*4frAsWF;MP-Q&0$;uQY14gTLl@Jvu3cPJg=cg9pm2%QQSB%H+SnrCM1|7 z`IeS~Pc|w$eSC_FiXBzoz``PLV{@}35w!*h=Z?m`dz4`GqqxJRW9xJ~d~P7HytMmg zcE%R5BBVJEv0cK@u8d!9?5|k_=s0I&v~k39`>mj`Xw$?ksLkTxs!);<@pl+}|B?PW}o65>q%TWl%lTqIK69;XmYKF(hPlAj= zh%4(B4wbp!_Y0OQek+p94#QIE(gi7A@Ybyx0i{8b#(8qQLiQWO;>RA~RIx^~o9p8w z{*VWHB7TQ-k*6|$eoBWRK6uy5)L3t*zhAS%aN*>WrR*@L&{x=ytQl;GuEnAlB2LbK z>qMFCnz{A+9r!SWg2`;@O8X$hww~d01I3oO^%OPLUh*?nI0XcP;9#GDw z%ID!BR^JSN;Dhc_-#2B6Y}Vd)X76Qi|9%-}{iP1pkiv0CzJX|?{Oq_+9R?~9Sp@~& z3e}Uq;df(j4lBiV_w{L%-g>~yM|V-@npZ0Ur7|-W{ zAg`voC4S_4CGBtC%o+VUde9<|@ zVYY$GNmPEKpb)FXC%O;F*rdmf(SzNPsMfI~M|A2O{QdpeNrP@1)?!2;w-ey|_b}st z5eNn@f_l|QY$J*R&%wX0*RH>(2L&2%xB-Bm>1zk;{hpbri3wV2>MP*?JURK)HmyNw zET&Bgs5A7}?4QHr4HA(~`7xa8>*(DqZ5ljjm9rU^PzdW@Kpm20mmg%o1i#n^$gS?~ zNAPs@gRHiWU$u%sbn_1Sz5-)N+f*=qKi>%BJfjG~{B zm3e&oMXloH;iKQ^^1T8{D&%0}&N38k3N@*pbS!UbPz=9pzBx+g^0?f2;BloEO9zqzI}ZOyTUPNFgtrT2w!eP7W8wv`jpR;hH$e1f@Oi} zyYzi0S^lY#Re%vMVPm5`mjM)cCieD)_LfI~i}))&GtTXt-_sV-K7i5xKAi7A>(y{O zD2ouO#4%_mSmwSj)UY6*;$^|U^WPle&hKFw`S{)i390YhI z<}&I33Dp<;eh)fhE%*hT|MSDgbb{a8h~ux1 z_$!AkQIdQzL+l0&gLX2=55Y7076{@vIy%Ux9e^_dR}x!SXVe&U9zf?ia&q5b_&cyq zhj9<2mf9-@4hJI(wn`AyGU4-`@)n0LU38@nEqX7k1y#s47mDaMrWt0IK8lx;PhufE zBPc6uciUVlHjfKwwfB9Qo4(#wzyE6xRDr-FN{0??%Q_yZVko(v1(<~nE#q$MhSPyjuGzH92?A)pwXv@k5MwK)}oLyR5P-45^7uK)M;b24*a zyuY&ID&-TtK%#-snrkn`Ot;-}lD`Gmf#BZXPyh<(pFdL#;Q$opvJ&OZZ7?4wPG=FM zBLI0L=Z-&88tU%z+WR|y;fVLtf!pA2DP%F20Gl%N5Ya7xi@OeUl;d*sfGe+O04gsX zBgwtRYZdjQe?8LoKtpbpg4fjMR-% z2(tW9R#DN;(sI}+?NjOR>wpGiC{wd2XZ>i)%1k$O=YIJl`M}^u=Iyc&Wr0a{y%+8X zCE$qvGVusP)97j6cV<1fQI!g4UD1BmoJfR+^#dFLUL>V{D*yfY6)>0AfT<6?_EnIB z7z2Gbuk}6Ld5M)a$&r45wBIA^R}cNCzsKJ94?0Eok)f<=em&L{aSMt00`(&VrxYty z;@8kUzHV!~H~e#Ch}#wpo^hb%-ZbYjO!iK|_~(CpVHFQhzIvz9?d`^9)A$VY`{ud>;FOnXz;QKf255?16cCZ8XvGGoUm zVk>2SS^fM(Mp`eWsP$mZOUrVAJh@FbHbcdvdvZUBD&aNYEM)D65z~tFGYD!gf5IL& zYI5EP%+97suWlnUDmH)BKisW`3l9k?0DC!rDv5}R!OBlqLS6<5nw2)E6#=SX` zU!SP29@-XQLc{P_yw?ayNrarj2-Dh2m#j!cLqCe6;r&8!1w?>6itES!`xQWavpq$- z23Mp@rT*LPDc41lXlKbY0_H3V-yJ}4sHD#kcp)L8WT|(?VkZCVnd%)98tx^ZT*gcO z-5=#q;@gS@7_)m?ztA#ne^wc!UW+j`g9qwFXf0pQnR82O9rr!@v3DiKXkBX1VOMex z-zNEGy|oFP>0DZ#IW4#};FT|Dv}jQ-wxH5Cs9!fvPq!QOWgObbZm7v0nqDoyDoj1x z*(Of2UJah_o~sRK4s54i z(uLrBH(ZWcn|KW-z@G6|xbUc`xv8o1i5dM=ir4s|nX)Xt0Sy#%(}<2LuXLyFbt1R-d9a0AW-*)45R<*Yc4`Yi79DCJ_U#SEIxs^BS#z-5<*EOBSHsQ%U?9 z6v-zW!8bx{N}6 zM1#{3M+b1@4>R#kXsImXeCyq#IfqXW&VAB${N!*~w__LPKqVY=HgDeffDr($Cw(w4 z{Mxn0W`n2KMmt+Oj@CK=iY-(*JsQ^ZShKj(oD~NC#h`jCJr1Gkk2{g46THU};(h@B zjHOdNvgcv1ibnb)0bMsKsNg-!liMJ7(+T|`{NgYGVTPZF$8e_?9bpFqYlsYrK`;Z( z1n&6K!2VOWfQa}nucaPU8g4udY@a|hyXy(Fv=ZARJ0LR}6TO(GR`R&O6~YL-T(}&b zE8kNz{AN>urIuN~!8^kd-&(eCU$2+ll9dg=IyuFfH?%fL%+$_w`Vx@$2MiSxo=B{& zSU<@F)$eSt%P-tqgH}gtIqqI=;$Zjd%jJcR?tb#B0Lvrd7n@7CFJ`-41w*@&TeTYV z{u0Aa4%BXQSxmdN@FvVw5SwJtdX{P8Zl^v@;ysYBB>DzR^Zr`Rsp<3=|%4?21whj&=eS4%1zeWRtj z-|~1$=&5~;r(=>@*(Ui(R5i9cW#M7vRx~1Y=%Lll-2n@LEI430r-;^lN5m+)GxQgR zo3$t_P@$V-j4-i12c|29Go#iH5NbTl_)5RanYMNLNtAes0g$J!=;`Fv_XArsU+|+O zC`C5-GZ5ThBAPfN|Ei~{!=lYSXxYVGtfKQ~jVP_?RJk-KiU*3eJ?koJ=r|JAiyoFc zqkChC-ek|i+Ou1jqll;mt`aS(*k+1F@FH9y&;%XSg57=Q<{4OI2`A`(z2J*19AE;@ zQ3G#BOvQG21sH2`?g~TUnKT0u{0sBCZl_@oXrS>^qvU?*EdqhyC4C$h4M-9L9nB-(cwk95d6w(;RD)(@hqbiAWsV zG$_)q7Q-Xw&(FA*JyM;LJLZ!>>r6HPimsoL-_kaQoq{w^Kc0O>>^*Q#&*i4)`eR$* zMP0;K#^K`r=p*kv1qY2SNgoF|V#aP~MzP(kt$q`q;{Y5WcC89PI6znI!5pF;u6J&o z;+aB-m%Z9 zz{OT_{iexg@iqk5(@o8f7NT~Yc7prAnv8t!DGfw?Cg^8Dn>I38|MJe#Nqe&)l$`0# zoSXJKWznUNPOJ1O$pZW%G-7DtW5K9<`1^lnu{=S1B)jQt{An@4>sWX3jAzCj}y+Zu^s7q{tS-DEN4JucvW>h$bJ4veshmOodd3<6&w)*LOC=zGB|G zwY=VCaC()H6yGYo#!@u!RulBIzqWnO2=Vv$<@{}QbUl{+?uP}%E45WsZ)}raRb!yf ziD!5{)tThgJnBmVvhQT&Ct$ZQLdg$pyrn3+7Xrk&nq5^%Q$q-@c`1lr1P8S`I5sd4 zQT~^+-Ch=I3g(uc`+E1;_K)q#+6X~Y(d%cvc>u||j~RbRku(@31AM_+0{4UDulP6u z45i^}DrOV}H$B0bIGgnaU&n07_Y)Es+^U)3L@b?@sbSc?VA!hkn4K-Mdwb!QY`%Sx zySZ5Dm`B1mPg_a0&U&M~6-6WO-=2J~ad9ggob5oVnF;sN3arDGL`Kz^M=Q6;R2#GuF1gN}m+br0|-Cfio5rHv* ziF4sqYH&s=6|m;AS{1YeONK4(jaBBZ)XYKJ8oA9EzNP9xkN>v)BQfhsTw}(5R^f)Q z#*a5^k2}opgO-8e!nHWPY77uufDeW^p6HG4!_2!J^b$K?2&eqY?c1LRA9BQyGWG|V~IRTGCdr0 zwL=}cXpo+OL@7+bO?y>NN~*svwV#W2wh?GHHc6|pt> zm#tzW=Cc~2@HbHLK)YD3QleyaV1QaI?~fIFK4Rlwm+%dEM{j5?KAVv940fDfVYT1qNSy! zlI#m5SaUuf{aMW@L-)>@3}b&IIee>W}FW{ z+w5xS-iGlOO{N{u+5i1YSmRa|%6?c3@U4Zavz-{o=e{w=41F5e zy#Gh_+XFhMtBVoX%TTix)qTskh3jiVz0bOn^m38eih>XC=-I4?D>1E+vV8jFE1QGJ z`AqSH_AbzqZV1$nF&S683?>axI0iT2 zIpmiB%*y3M_V{K(a5fuCg1eFXG>ol&#;0i9$;c;t%ykj-|gO;}qE z_o&Z;9$(_R!I=0#x5Zf9_}Zi4mS+TPbFMw+={6U0SbjtC<0@Dp^uNjR`lHHwsRO|Y z>Lg0>qr2M*`EUo?#3ey8U9TFkvBiX1+s}CWOS$E>1NW(qli~-n^wu6_GNAanWzX8& zw75B`a((~#*DG3w6YLJ9aK)Yyx$#lmZ}tk4s`geLfDLedJ#K==?OGO|w`VoBdS_N&g3Q8o zMy}gpdt+)stAq7mtzx8wuklcdThVHSbt}bMtaa)oyA+Gp!r1<a^OW>Vw`g1A2y)=JQz%@Y^Zc zNQxeBA$xR01yBv&i^rJ6`{^}5$WnCWBiw>Y_3ir4c&+h$tLS8vOpo4jtjhfU_?D~7Jl3N>{R68{osjmI zesb2bp>h9_d=OrnWE9mpy;4>XnMwe}8g%xLe0QdjfG?J8$EoK8)dnq(+F3WLOgzon zdNq|ItV^p3-7J6|9D_EU^5a=AfnDqF-?-YnyPP@+8|wvGStXBQbBz@JS#Em|uzgBM z0OZY6w-H37UNqc>zVug~zNbG#&+6B%6xVmKsDkx7W-b;_b$8 z(Q4Uv2uqc*b8D{W<+kwJF0zM&RduMEiOuH42yw|~TeqMl{R4tiyHb|5a)pNEg?C?* z*1YUp4gVkT^E9*4G>h&#ajL&`JY6=AA#goNZUbBlbnPLqeHB(%p3F?& z505oSwE0AOZvimx#?OsGvX=u&^O^=ccMv1Cr&+oJnwbYk5?G>cPej22cn^*!$|?mw z6moOHp@-{YqHM5B29El@gv$=3SYVs>0vk|yzMV@o!wF1qKDu1}q-on@0E|GvdD4pw z`Zht6`0M5iN4{jDlF_dU&+a}xos6amek}QZ-IMTTKHg8l%(q?Pf~J}`{c6uQQr=P- zU($IA3wAHD>Nxyk48DXrBVdBcmCEXgtn24-@)^OW=22M@>AYe@y?(L=b0gj-3i^4a z-R$Zj*RT`k1J^yTu^jZc`;h!PC&13xts6dVfE>ZM(JHFrfv8hp*7vZ~-qJet4R>bg z{lO=XKgB``Z&l3?7>K;iH{e=DIg6}xa7Tc&!5&iHIK%$e*QBI9%qd2R{O%Eo-Fj)= zNtcF#U-b$p^#~mD$Sl+*w1k#?FE!6J?8bztcOMXBx_%X|68hI{$4tQ%TVh=tUL{Px z#lfE?iB6P!Hj1#b-PLHpWWS(fS(1!asg)12>ltGn95^}=>GBHwiS+VJKJJ2c%Dbz& z90|ls&e!_K1Z3!MGy1=(X6Gh8>kW>l7^jvlC5*qI1MkXJJ?pz*<>|%V`3_y>b6h%K!JzbMLhq zS#j0-I%Bb8%}{JAvBMqEt7_1QV}DivH{=euX}*v?or5P!ctOY*Cr$}l8gC|aq*}CS zWmQH6J7|8%+~7AEex@Dldbc|`^FjRc;ZE;?T|Y+|yMBjOj&12fQqF>_r0vI5lkay9 z>ejxCY%t)Ceqq~YuzKZ%bk{S1dTe1+)YGKhHp}V#?yXn4=XTdvqZLBUT3HrPIou*T z5t2r~Pa0H7AVM4FQj#Zo3Y95{{l>O*5SelmxT29235QzGrb2Q3aQ2p_mC*Gv+ua%M z-7}Y-$mom$4)1yL!ZUIid3mzI9z_6Q@1shlKc~WZf3Vz0V>nB9QNPw31Wl`>fs(tg z{67-fBq>%W+x4M4(qqvHtefO)pMBN;^Y}CEwR?mEmN#}vF5M%X<0^S~mQ5U= zOx2?=Gxt-q0`n5hP>=2-1x$pYmhP#*K4;sSFC3qyz5FWujF zVf!1nCr_njVgjf%^I+T1Di*Yg-C$FLAcUa&W|i_@2i&hF3zz{2CJW>vXl4MDpw;lJ zf=4|8xeRPTf!BHk{sE8%EPe@v`m~r;nEHs~|06w7-gFoJ6T9~PWN`5IJu$#oPsSLF zf8l36s{!iKDGnTvZI-n5kG|ikGyW?&A7QSJ{F7q$E%^TPZq^my)75hmBldkIU1Cc5 z>xMGFrL<%5tG_-H5PWdoS5c&jC>DE9ckSlv4C6EHv%K{#5k$|1ZW(k_zNNg`@9|gk z*tc)K{-2?1OOpWH&MT~}`T%2q#0dDm#=w1n&0|%dq!Dgef&`wbd)e^YDcF()!4x1< zWcv>UQh*84z(xgp5Klqs>79kg+V!};p?zU!cgGIIIG~Hz{rQHXqodrA4czk07(MS?+~41zXzMJs8y09gglTUFuV!sPe{u&n-G24l!i zz#W+mkDrqnK)57bZ#1*@YJea%7E!$lB)QgbAw{$h;K7N`_W_W(H$=H=HU$hCF!crI zVGg``VUFJK)>2~c;Qz71VU@P~K?xfdg`aMSA^!jOkf7WETjj^R-oUz;(S^QT9U|?c zM<1Q8f0Ff~7w*7nzX#I7m#~~L=xnyjZ z3M5ECcdb`8-aczx;%A7DvT_V{T=G7}e{=iE*kd9W`nZ0>=NV{(vj~U2Nt!ZEuf`K1 zLEzZe|4q1%b6j zeb~bO|NWE8w?lqkuJSGnyiKc&1GBw$&}RT>9`eh#2`)u-%NJ9VA!f;U^%}XIFo*oA za<0~wfxFW( zG^M28MI3d>GC6Yd2=m;nzcTB-mv*E4BC<9I&kEVU?d=xBd`81w%8=Qkp^(3Kxjw6A z@)AeB1Yjnd&>aEe%qq90a2D__j)3phsKI)2N$+?iumGojq;ngM-}@vR44vIMyX|i6 zY3XrIO+R_yM@5?h4VUdaNs$$?^QnaSi z@?{=4Tx01Us8Y~ic#q6rgs-R}mNaSVtVWeWP~iWM2>STIUlBC(*$@cF)CQmNn+$=D zl{KL0`?w6?2H@dqa&2_iG6P)(LJpdVnJt7=1L{nxUQ5KBwVt~VY!YP21>UB%2LoHW zsr|!y39pw>nm4r*t-3ZHg}4L$bisVuTI6E;ha1QCx%JwCy9K$%7x<#T8y#f|9f*xq zXSS*{n2#{OEu{LBsrL=~BNnC9{bYJ~&oKspI5X%-0Wg4DMHfjQ|z4LPhz@fE@hy_NCUMVu55$-)Pw}fet@C7Rt z;o;13a)_9jT4KU0)j_t>0W1$y6<;q0FMda(5rZR*RAX6oIfzYVX)dU*#@o~a0o46>mEGgBXS!M zUPJh_cc5QwjIB5$Xu96YDqdS;v~mv+$x(eVWKn~a4JSx)O(4K;eg-qtQNbqQOxBd) z2Fo~GZTSwIsc?E)S%`vzF1y>1X~9l2 z7_3;$^Ai#iK^B|2)RJSbLio(5?R(@QoDjHb;mSW1p_Tbx$@1XuLKc#x*m1<=t28(H z2L^ca{(Y~3e$2VvvdhueTEqqH(FiKb+3kG=N{YcgxwMs@5uf{KDj6L3UDK|n;h zh=_oI2!hloReBSU60lRHND~Mky@Pj6Id*GuKa`IU;{OCl9WhVyB7I+I>ZaxP)2vp>qGWjss@BH8usN+g@UQRi;iSjCh zzHA64g4606-luXuL(iS3A@gDNOGj`k#xLJTvyUJOKp_ScAQVozFd6ud&ZF-v4A(;0 z#ol2uJg?9J7_RTY6cZnBT>S}AcRbV$Ws}-!to+E-yf6|`kij*1=(!>3s`XE zO<&Ff(5cb+b*C5+bh(Q`3_z8uF2#wu+)DxT@*R}ua*cl4wi}?_a@v|M6dgUyC1~h_ z+u!PQ(dC{(55CV=<{3?5-#pLZmBxP-xx^nLS4vd}x_KZ-t~em{4Eza~cwUH@HMUOp z#Ocwu@9XQvTQo&&Tl6P`U2CSgcEKV0&V5pl6bq(lG<|pt+0et8cKv1QOrkz85wLBz zA3VNjNRettBk3|QN+R{r!--y@arZZXARMWGC<3&GA|BAT;1YfVXckOYKuKZ@*Pu!& z?Wd@lf9|z(H-6KFz62fr32o?MF_G=q=|w_hFrM3N29xM6u5 z2oznit}w0_?6+tkG5{{Gm6{QB=1CL@Z4T^*-RC zn>t-=MDA(6}Z+f*eEPNDdS`1oUO=XEjiwT|Gtn)+M>+IAeq z`iODWi`7OqIB_80Di)sE9g5*>S^lkd+B>@_^xhfMe|Csn{)**Sbm=ggL8&b;7xt?VHz27NCJh>e#y-ZAw;%j(BjrBTF;Z_4ib&n_R3rxXcsRw=lKuq|dnF=gxsq zYk3WxaWp??TCsY;6kKCs?}#euJH` zlIfF3S5qi3yLbd_`R8jPwB@gBkwTDGsMn)ETDes+N(HYH@!NsA6oR@bFlhj-;iO&G z*03FhOBwX-9O4+5eF1xj*<}Ye?>T_$juq&)G}EQnzPK+|K`)bJ!>;*}-#?#09XEvrQ`48O#D2i& ziwwnKl3C!AL{%%w{0BEg@-H`}4eRLQ;!-*4#))-=E}pRmm?f|_P_mGLxB?tjaqt{u zH+yc`-E2+F`>GYfhPVfl`!@qwufRsbm$!>CX1lNm>*!d(euQ1iGfNeG-0_2N!9U*c z77XRC5+4$DM^y-StWa}F(cqPW93eJFc{6O5N~jo&rO^)Ycn<7GlEWum#x9BsRetCE z-cP#J){Y=^;eXfsx*KoxzrQfxGXdhfX`8^iN@{D_>hD&NCx`u)ib&FHuqN7~W^}KC zfMLv__V@y`YZeJe|GYRy#SLJsRzp*$zQhkQKsEU4IpToY>%**rbUbms`#AW`9~VcQ z4!A%g)51x6$lADI6l8hFAIyP~ANNs&bE&;ONsxHL+%49)EZGShmzBy(N&js66W0G} z`oCQzN;{97m*ka(j&Yz0wQVR>)_l;6B|+Bfn83znEuu7xPL<@_3>$6+i_^C<1aPZX zsB|W1#Je{>CvAY{(NaQuOw3^k^KCwSaWqur>*u$>nUcS4Jg#4IK2&V%%h5?65?J*; zqlvbt$@XK>(my;RT%m8OEV*}1?4RBLp5Ok=MUUnY zdgShQz6LYO`nBcq7U;&Y1y};$qQHTAg7!*5NtCFja6^@hU5kAqJBAuc5x|K&DB?b=QF@7`k>=1z#CHN6B?mBslpA%F#qgmc)4_iqN zKa(%jaIK`;54~K5HbQ7J`$J-5-j$4?)rCj$koWwjtjexGvnokv3ebu2SR^+7ZfIEY z;b&dqUw^H`^edtuDD)u3@c$0M{mCuLiEk_4!3w}niY|{imXY*ef?JM0D<;XO2Ih23 zy&k4qycxJZ1?-_r0tew<5Z3hlJ2KTdB!>}wBr?&g0Ac{u{yi|jMKBq zEtAcn4^9D{o-6Y?h-}9;-X|N`BlzM>1#~0$X}h$I^3Yw8l5@_U>j5YVenrgvduSkN zd58b3{m*u)@V7thlmM6@#8qj0WXH&X5{F{jHdYe0$V&#CCQxT!v*8B|_gny@H-7Uz zIj-|Gr7NxjLjBpxPanMiL*vT)2I7hFjrY&=oZ*~gY3X9QqTy6b1yn3{KLk(LC68RQ zv6+T=?*BgKfqPXB#33#vuVu(0=}xh`-;dy^ZfkSJpaZSwCqMu%BmCzrW~@+{5!jC@ zx+@6Sl>Sw4qPfN^=1OG!mLcOF>3Z)mm$Yu%woG3iCa%KNBrTWFEG!As0eH*el0w(u z7S{g9_{;mR_zPJJ`1>v;H;{nx-VL~_kTQyJy;tW=^agzOWU?K&5o-Wk4APG$bV5^$ z>qj8q z88l*ygBks)& zYUBF1kb%DCkyuLV8FlmMu>v9Yaf?cy+` zN2#qGN>@$RF_7vA3RVK8bRD!3B)TEi0IW!S=_yBBR0ebry4*Y|MET_Uzh5eH!|a5O z2@qI9uhvt63<&$Zk?GumdNGQMeP#9u&W?V;)!!3RaQCAWPk>y4VYLBq0~6J`1ig1n zyI*%oAneW29+ohxBNfAb0nuR?w=$Qqo+Dc?Gn;5Ir6Dmzi4s}@;8^Jwknvs6jnL;S zp=yCvTq7~g)krS9q+Zn|*h$Yo2;FQwxAULvh{U=-?Fb888W7L%vCB^)Bek`)0a2Y~ zzx+pUY+yh&L2nrexnqTem6M-3E&h>R9V4cwCI8H+C;kgiT61!#Hbg#D1U#o7qyWZO zSkk!tz{7i)WhPUEj)rU1_#p;jz$WXtE&sfRAE_)vqw&i`Qz}lN@J0h>GTrzlC|i$z zVR;YaRS<48(YL-`&e`|ry)V-H6Ut-(uXC}y4QT$rv5i9w>X6?U@_)^YCBb|WMAZmv z%!63zkd@PhvxfkM_)Y8NWb&dAxr*?@j* z+7_~OFEEj=5ahRiFkFNdgl6MwoI)&|jgxPH25cN!CtWhrAnSbob_%l<93sf2o6u{n zq~t;<5+NS$^{EeO{+P|{>N7w^k8CAF!+4_tynWq3s@}w3yIzAWDgg~!8QcIFlq$ev z&+ER0Tznn6&XGvhJ5|*Nhc}r`zKSqlE}9m%=br8=LZLLab^~XXq`+^WtO-RQ^ZrDpM*d%FMBZ(GPYsqZ)`uL)zwJ~%GiyK4$zaSH%F5jw9+xH-5<%dALjh2V;nmMUV|q+|v;eH53!9_}DCkO* zBD@eHCM0qTO@V0$lCtadFSlbL`h#>&qp|-Lm~H?$L*RAP{`!XuqZ1$G^`yZ%rXl&_ zx?N0IWa{%LK`#p-BzwswnK^VdGb>9IqOrM8uc5KylA4+=Wa+?cA>`;bo!PKO#NirL zTVowm3#%au38BDqfUZC+XboXHXsWJ78<^+?Hq`){n`h+|KKesiA85Yr!7YXqaVdmu zkn+%NN7$>}XS&tV!I0-F#2dRXtvk5a2AN3xN^hab2)WL%xU(yN?yrwhQOh_|NriFP zwLJguSh27z^zhd-|F1U^1_h5W|MHRR)*W2?{yXdNKQ8Y7H;+X@Xj+u>TlV*1-^^3d zmEY5uKq#dk6GswY!cUu)WqO!2FQ0J zL#Xc*efirPq2~ksZ7Jx#@fcizF5BEYaE`x-|9@c@H31E1qP+RUhCRU`Ew{|H5d3k)p&OyYr`73jc5Hq&v}jtYNq0R^)UH{~LE+MMI+-z5+2N z3dkn@RK)*hku*fP*c#7g9ypU|LZ@ujt5dl!|%3!*%W-=g~H-1*@YOjfTK9-&&Q=d$o_czT1WHG z$9?zK{d`;;xAOCG%lbk*;i9bz0vcyXbKMrZ`?;1gj`g192!fij7!sGl#IE#kJ z%h1PQ*B=+-Tm8@PXfT*Q^k^@DA*j~KhZm9IH>}+wt+Y+ zzWztF&R3GW3|$UJp)#lH9}P=sf?S_B z@x2>#{kqCT+~J?a_y5^~|G5SKBMARL>VkXc)oHl}qnRx;1lP;BM<3^&hm!y^YP#Wo<`SC>JcOI(+mcG#eN93*47*VALgX2g{HL zn#+fL^$r#g+={n^Uo-E`(q1Sxw)*_hjoNTg)O7Zgw_~K0P?_?ckzGjN0ix2rrt8E7 zO$2w&V~6|9-eefpht=5*aFC*k{m0v)RCCg6PN*BAuQSgbgO0=Z%=wp=b-dR#w*I0; z72cQ+#^BtBY<2T{D@(?Aj>eDlcn~EUhBP}}m-p#SNf%tf70TYJnL_;6=|#eqdfvXYJUJ67*Y4KHkZ#?vD;L?@FBD(0SR zX7%#BT75--;$2Pjoa&Mwt~aSO!mQ9!>SxyxY6^6rKpG;%|j=YPaTmO=2Oz(5pxce743OM zh^;mIMg52GKWdl~Z?a_ba7VK4$V5N4;PR!$tfZQp+5)BaGuojSRhtrohu?oFmQ8+0 z=!txgkB-(9Xerwwodr#yF2efRXcY~9Xu(6wPi-Q^(EaI3-mXC|6Z%$~82o$gxCBL_ z+K9+6D*fV}Q?{!qCX~%l70YGJ)e9!km1-fJBJS}9tii9Rx!P8Wx(U^fb&(IPAS|8{ z(!D)H^k~GHr%?Zpe2ld5A*RCP31kp&bex{JJr1q>$!p6!)CMEy``Z)t>*>C`Y35a+63_?XBIK#2J-0U3jG+4Bo55!P}>z1rsae;ru)7r}e+~ zld}s?=`eVDKYE97)$O)7(bS9Q{^7#{78f`~b%i~n)r70x$Tjw8;2tTE+_$sIwN@UL zi?~epdg6pF`nWv0MqP5s(ZQBs{C;^&gY7WVXU-qpEL1ZeEZoz}uW}eG9N7MgQ9U>m z+5MuxWd4{V&aj)CMYPAnFHO?9w%3~aG#fgcA_EAE3mw7J492VbVT4gd%hm4Wc3INB zaA}oP*ut=%Te#;2(J)OTUh+~UEq;boc=%@6p}UvqPi}|&;zf+eLVNo3A4MPW+L~~q zql%0=FiWqMakC$Mf6$&dp^x*OX{TNkgZ^M~=Yc3so<(j# ziMRI^&T3yu%Z;=1< z=nVAcP~GY)TBs7QNBM&<>$zEP#N;>V41W$ark`@`_%L)J3+aPJT%QpQz8ti+ZKp*2 z5s86YGUW?T>Y62TyPExtz4A6-X(+ee_9@k~7H`YY)6vy^&s=?s0`*}T{hNqPCXUT}H6}zw67){EYf4QWk}ZbL zW$OM!5td1r<(3@}H6-gQ@)(Vv4LaI!=|jc6rN0&14nyxZ?wdxOSi4kzZ|iX9Xyr(* ztrzot3sP+x^fjNdr`$bbCHd^^-Fw0{)G~v<{?gyH9NUFnei=D?C9kKs#jB$$pW=_C~eIyhJLw%t9MW$0=Ces%v_=*R#Cbbabwr$xuh%gh3HN;(@+Ux z&#TUqA!(!fK@dXPmRo(}dX|=}6*F1b%bLuUY56=Jp#6AcePeJui9tEo1*R@B{lWQG z8dpY>&T;oPPZtjr6{X;$Z?uFAxa9UE0rSrOhWTh)6Q|EgF3Kt@Rv1s+hsVUZzYR<$Ka~~vo4zzRr04eh=6c5k)SCs}CNJhJ?dzI`;U zt9f0oxLE?c4tYIo!ZFL3hkfu|#+EN!q$GYQS2m$r+70Zx{d#p$P%`riyH##a(^iyW z*Zil)gLf!c^Y4y>JFr)ZM zd$05dEwzC8XqyK%h;;e=SJhWH6*vb~GzbHSoxU!B&h z=^C=2ebw8xjlWB9kc@~#pC?LtPK?B&ybT$X`}B;>GxCPUWoew(;crG>w zm<9r&AHLAk%{9+u5yza}!{#l|_f{@3H4EcCw8>^!r|g`#nFMdPUN|^e&pj+M0RYe^ zmFJns;)bHB+PD8^iqdr3O$%~{sOM+Wvb*|u$v*9N(G}y=kjEv3;%xQI zGD|mye3nDlZ)E6X+sG%KrR`)-Akx{P9*T!Z7Zu+XqFW^jo3dCe`%7D1*!Al(>&j#Q5ESLlL3dT- zw|W!rnAWvni_&j&<5lzp4kR$9K2app9?J9+^3gjIT~r%b0_>*q90pz{;>ypSc3@;^ zl`d+^N!DOkfu&?H-C~QkKq}R~_r+t8DL()-Et$%b1Erm_(Q2`IrVdxQeU`p;CAjxX z7xP0~S4s5a!3P;JJ3HhUO+$v3-A9Wj@~)H#;bw!{Gktm8`FxkTz7o_C^URrfi*Ej0 z_L2qItcjE4^5_Uq;o?GmYripCJkm{MiLx8#O-6Nh7e;lOzBQ_On2+wU@AGEV@Q56i ztfWw@7Z}X?pe9xUWy0?_=-5qD^cJYa{@W>(PW1Bxv&t&wi|=4P{?s6CZ7eRB_)W5j z%VUV2iix5K%uTm`Odw^-EPP?18q4d>vpZKgSGe-cPc(aUb@j-B?>*hxZtO8EtWkRe zR8E7df0s0m$d%Q}XuW-Y)Q5V>-rvNriu4DeH0u+!8Z8993IIzcqu2IDJ26Y2m0jX+ zs9Ii*HSr@pzA}xzx2kJ3*Pt?@QLi4C``&eRdenikQs^Btl@Z-MD{$tfFBr&HK ztLg!V+(GOMjc$F*pv6cG9W`qgH8V;5{JB0!ryTU7kSfYCdD0t%DUW0oH=i?IP~zp4 zot(da*O^E{NH$u18XZMDn&%krRnJj^5?bygn>xn5#ukMO4>wK=qCx*Du5m_t?Hnfy z>?&ntzTDw>%sG#66%=ZzpW?HjdP4j5Qfk4~B9(@Y>6{;Dz75%MIm&k1p`<8^>h5`u z2fX~g);3ba7koAu3!wWu`Np2839}h?J*E!nbk3`U=yc{y0^V&E@KGk-tBK zk;k}LT<*_(RMOR?%CqkQ7}va~2F))oJm*_`E@JLSTW}T5C>pY4;*j<%tZ=c-$i>zv zUhzOT+k3>DbxPj_XD-fcLa1%2M|Ls6tW!cc1h#O9#+FKPBq|F4qK; zK)aFM>0FhRDof{gkG)vLSII?n`zhQMFlHzHa~zBIv)q^^=nY=Oxhta809YrD`ErU# zE(+;Wxhvn;Qbx5WOmA?#<5g~ra*;3_Ez~pdtLMxd`Vu4pe52|4z9<~;DPf=D8~t*W z7sbif%?`Kimi}Fz&MM>+PpfJncl0<6b(9V}ebXzSHpbm91mmAxOOi2JZLc07Idv^g zerzs0S)aZ5U?i6Bvxd1YX)DGN?X6<6PXB= zCheBrjg{LwQ@F00x=KrE9q~nZr$zY;@<^+{U;x<9`9Y5fzgE~UUjH6`u4-y=yUV`L z>E&+%N%kKyBUdFT?w!&ZGY%Dvm_k|5!m z+`{dn^ZRATVgTz#3+eqaW@;dN`&;peyY2QJ;5u5NZi|muUFwgG!ZC)!9=tS}dm8B4 za-$u$6uF?;cxSGrfQX;|PP5Cck@MDVOr^1ieyco%iqd~jtp z58bjbq|1Sq>Xqk(!W}{5aFZ?hJmH=q7Fa)#SB2;g~AgU+IVQop@BiO1Iq77M}s{Y*|^OS;As!uy7*o`a@aKj;edlEBLWDck^%? z&gV=jDdwidxKHe(M(;Muirdg4-*}}cof_1DXS((Zob%d-40gj7i}Wg6*F0CEpqyca z;-qHnowJ?u6A|@EqRK&e!|^q78cj5%sTSdwKDt(%r10v9nvmEQfrq(hl+XB1*H_@1 z?{M*`Vms~-GFz75#%tLy(J9h{2PJQpX(bX?6bkr=4= z+DNqw)l=&YH?}%x6S>UGT^E#S%fyN;!U;)`SJaATB1ZUCJL!DnC-|Pdz=#`zqjLYI zUqi7XAK2uVtW!dkI#wkCA3eE5t@aSF5Q?xa93os>!2)^7SF>L9u$7~L5lchfF~ofbb$ zjXI?2R88HvIGJ}hTf$`|Ot6lVrJ!Ga_GTNqjS7EZjuB<5Cq|$>5Bk@UQ|eCho-QUW222XGcS~Uc^0?z zA1E?fyubY02PCtR=XFBNJ@VGtCbmb0z8jwyq~RskKuY6h^1wW|7Enn$)S`2;%X2eA z*BFV_i!A)Dn!e4kq%lyaV&zhu_(;p+qMA-+ev|eW3Ldt&y-^I4OOXK0>Q1m$S~!et zcdGL`xC3t@P}qDZCf4STr&0hU%HG}Js`hQx98ukyO68KUNLn^S>>4rg%W)I@2N`>P zOs2X~@UOCabf$c506TdQtJ7txZk2`cvRQ5t?>aUx6up!k?yLBYVAj%EIIP30D`-~A zA#RJpsu*!Aq?^2D^?L5L;kv_~#56AsOHBze&k0jkpQ1H>%3&?Klul5xFzp@Za%~51 zAxEBB(u~(zb%}(8&0&~Z@8YbcAamlN*&|X!W1|qt(OF5f3zX5V%e*I3XSqQUc>#gI9Vt(}d3F?(HOH%U#2NPo}&2T?ZQ<=nhq%h8D~rB5Bx z!E+1p91IM%?kWuCPL@E)kxvO~(IJ=SROTH#^RJv6bfUZWb1}9rN?hVgrQ%lGs)-_& zEi77H!HJbBMw*}A3gL)TmU#V|v}?t#tw+Tsgt`nTA%=6v)h~~DTG)PES6FsJFk&h1 zc(fjaTsUOI@ES^?p3s)N>Uiw(vuAIYH{N|(S8nj&_!7-8IJ|9;KGh$2&Um%4&DJqW zG%LX0ILF4`)pmwzOq6|^lT2-=i8bb;TgBqLM*8pzkpWAW2O!udm|e{OrNVOWLCZiL zJ79#pCn{rGOOL(LGMm*t*23c7#j)RC`_ONVy%o0IE8}-t7&Zl!jrnL{-0XJm!Z^q< ze0d$T%oaT1{X}~Q+v)b9oowU*R&t3)Ds`v6&z4lYkgIuDJgLv@7PuW6VSBNTV^biD zWSNfa9y~jTd#=ZE8WnKG{jRNRRZG~Onf^rE;RZ2>CYf(?-2k@*h6fzWZtNB^;a>TG zdsZcgG_SnM1#`rTZxT_>(D|6qGUGPjI$T4nI%f{A;65{Jz}I7g+*Ve&)_t z{0WPYU4R7aE(rn;?=I0)DpvlHPVLLD?`<77?#rLVc;koCO;k&ajVugmI``IG!F|g# zTsZ7}HGAQzAtmprm%7nQyZhXpjTy1Q-Xc9}kZ9U%$Yp~f$M2io!Nx)aG0+Or1JCK0 zlXq*?NP4VAJX-~~ehHz`uq95&P9)DtGO)g70K&6t zFWFm{s!vBeKN__(II#n=;&x`UN3*w_vK@v<%ToULL~sv8sS&M#k{QicKk#=+i+G=H z$gX}W;$hvBbRUu-f)Ka9!>yi}#uf!FD~M2)tFt}iCU`VX-~4FcDD{<~x5_yw)b^#z z&%D4(9l^>^X#9qO-g2>8H&r>x%Ot)+HoI;-H29ZHJ_=brz4Y3jhk2mlnAjJI_0x$d ztF@(O8d~ivXf{h?-r5JrmCbyAQ+yBd&>MOdQ8kpwe$vbSm$E=_d;gK<^F@9>8 zfwafqYI|K&3t+)`Zud!Qb268#Tw_I06rljGb>iIY%wGSH<;on8)|I9_N~Sl- z?|qVOy^&Qs))DvuXw>w4O8Tl&pGa0ZgpR{pfo{K^Ql6;Q=cdI2<=Z{?1#mc4DKW64 zU>f5RKE{_}J@$FF5F{$?EWGcq`xAO(lqb#@f$Qa^XG!$_>dDs2tf<_H|19^Ke;1`| zDuT(1p6ij6tiE~<_s4A@^XT$ENhY|KUF*cZt-mBL5y-p?iM((sIR8__Yf2_`hk>xSx1}^N_wUb{It?j^3TS}jG+RYYvlCilXA5F}ZHfkyVN;~i7yuID|q%its zH`qneg3hIoeAZ|yv!2Gn_Jlb=8UwXbK|4Bb07f8z0A_wYzNTUvK>tNkq zy=}GUw0lg^Y8&Q{!uFG9U$404d4Bi|tS=Pe-5O^R#^D^$x?bjKmZX_}$V`}gTPOOq z2S34HTR#c>H``~M?8bzvmafo69<<;V<+H8gutf$Bo@RZ;(~>=imF_LU3JKVvil^_K zZ+{+V^m-_pa{UrQjJ4hRvJ$u)yS5&ZuG{U1(zioAy<@pI5M|GELmY*aNnV90620de zms`~(Gw>!jL4d16e^Ab+MgO=BisIhza{d!Ym54yRpGfY|Z*d8b8U;3+O4mi- zk;taf@ym2p$O55-dCig*qV}$QJ`G0(0deu+Cl{^>S7x;sg{wpab5vh3zw zSH*WLGKTbX+b681z6q1V`bX8A%#=H-MnCoE}9Ux6pp ziF3`Zef6$!T&(YQeOdoY6>58e<)p9|(B6eyt~uAc@oC?;WHT>#WU7y)ti7}$Oq#JB3M_ZxS!AL;p9d%vB-!;)6_OVkA_dz2 zh&5S*_$`q6l4~|vQy!OH5eql;EK+Y7GRMbiljew(y!_nepQ=x}wZ%?6u1^6ckplA!Wdl zk89?qCvQRJ4pZ1(xgS2z)4QL!s?QbK&EW*qfn3LS7;Bce7(aewVpy!(8Anw|;aXp2 znw^AIIZ{^B4_N$$mJ8=Al=g3@8U?L_~7YUDAAHFCW^>F{qZ|i@}~0Lbe|> z4lWHT8a-%2v7Ap)PR{OmURRIv*N88^C_$Q*Z@s~UEkbg~6WvZZ5KUt;m z+!18II5tv3PdTM|`P-Q+)4-1}O}1+jk-yxQkJg;yBRKE=`(;)WE9*5AEj^}1)--ui z;!BjW9O3jfKM1Th@`)$=d)1$KQnvYL_b8Jo20p3rO^~vCj62*KUn3(nGkSw^;;tIU z7Spv88lP_6ea$a0WjH&0ZOfiQ>eU%m^0})vb*43Ak_ZvYUE@LL9-NoF&EQS>N8uvv z+NZ%UJSZa1wFrrw@=1b-JPHzadX9vGm_)A*xK|E-ylsJFdp;hT{`UjX#U1uy&qdUQ z>^t76yz;mXinwOo6Gb8k6>eaVYm0kEh<*7!2jG zBq^_j?3YImKog9!!0vARv9$VjQQOz)`DF9fc11Ck0)pQ0s@i=qZ!A8BC`}y4&Xv|1 zLBQyBwnLXE+pu#-5Z(gPU0&&xm$hJ?X1alIuXOwGD7xill<5fZ%J^*M?sqSZ-ZlzM zGMAs-BO;%F%_lu!Srv^i8@VcV%CgFiUXrbY@JFa}!3Ed2Vx&7V`D=!+ki6zUND)1C zbfkQjlq+lDsjLJ)Z(p0wZ)ntuYe!6Q_eH6DZ`D-+Uy!AqcXwaiY-L3KDs1jmeO&T3 zObCn6g!l8_E+y z5MbaO&fI{GrHV+XCA54B=Gr6!`FiT}mM>lSV^&FSWka_hbwqF_J{9g(E!W)KZ`Pve z=)F`d&~<|L)|)&j{EX3dVLWn;f1%;kWUz_HTXkBDQ?l;lWqORybn*^<83@X$16$36 zTB0IGEql$*cQU_O8RgKT%Pm|~!Kv&t1b-2?MGS1z$I-}qf*x4u%z9NhGfe@xe=BWD zjR?~rANFRUtAF;!=a1C;&s*Q&WxyT=Io|K)qopH5Vh}r;j2j#<_<;7Wt$no;LX{@{>+qI~9l}!!KVhigi zO-Vo|Cl{;lvvHtAyr1HDDmc+~=*hk~+V(xMC{HYI@+>F#-c4_Seh>T=tn6URw0d=y z>D8KpWu#7ir|M;ASCxoWRjg0Ofr-6Z^mw(B^jV={47(AFfIha!6SZjvt5LYmk| z(ycwK5XbcXLr`?PJtNlm#&PY)ulU9F=)hh(^0h{*mdf#Cx;+W!1sA)kEr?T6HoG0& zt4Fr~4r;Jw&9c;!$>p1Ek;fXxt2oqgF_4Cbrm?oDZ*1{x#33jeB=~tA0AR^@bkFdI z4M9oHnVt{v;XRzUveA$fG&Auz=~SjwaeT?k7;M3Q@1{(udeA49_Ew1uRS4=@3&0n1 z^hy+dP>}R-LLe(o*d9vOUNdtd+K&_ez#;yV?9*k09q%@TC7t*?0sv5rjEtmscYVxI z3)?R;0+X15B_$Z?SvjPPb(|QEtwf~A>=8-fw_4&2XLM=19-*kPurGX2M^<=FU< zG}W#rnkUp|Z`ercKxc~A(`DOc8|;CA*Ahdwf`@Ek%4}l3pT@9Oa%*S8Y=OI!2gxc_ zZ%6toqF^`L(ZoyvH%pM;Dh8ExgwgL^k~vXuZ95TC*Lof|NCM%A6Yn=1Y2JlbRNf-H z#l%nSOTCTqib@9rPi6Jt*M99r`IfX<-5^1*IXz$8qhd$ywB5j3b9IJbK;T5~tu;N{ z9ea;tO8`YhIdtI=S2ywr86nLDmEs2Aryy}z0W=Xj@?k{AQAGF`W#^uA$3bC@9FGQ0 zzTl5N9Q2D`K)XjBIEz&EJs--Ax0JS(`71rSXoJ#1*?5x24NLesxJj4kxmHojn`e>C zmvDitB_(rkLZE!3&uXisYlty;@e}tiTzUaHJ+~eRwM4e$d9!K$u0=m=fi}HVF(G00 zRzr(V{@ViCG-PQ;i@eb!0=TxG%6A~VhXbm_9Tg;i*{H{#`rxR81w@bXcs%oWMTY6d z$La3KF+#VxO4^|%ZLcenUJf16hBWB8A61a+U~>ykKT;UCa`{M+Q{*M_%luBuy81 zG{>85hZ5PR(iFGUYfvigwN7eJ9dNI&nU%Oc&Faiox-<@J4d&f(m0do3T%he3lQI>8ZCD-Hs-$ORHfJGo8_Gv`TUOsiNhX*-${+oKX> zY>Ur`eHrf=y3(#GVh0^1bw#ULy;mXVZ{mbvYK0?UwQ0t=#M+|e>qv55GL#@8wzR=) zL#Ns!$%8@Iq9*Y5_e<0JVT;8b8;`k$cnuao;>Ytd;n0lBPCkftPj1<=_sHeOm?hVmF%s8@lSn0>eQYUfbUmrE=z1( zucDuo$y!78qR5@c`CX<9L%nRGZu^sxq0<2@7Y2&Zbw>rDbXu96e9@qz%tBBHA;Lt+ z3->=81dhsm+O`(nypj>XnbLAO**eWMJOsU-b^YroN>Jd<9h%e0HK-noY>(r~ zJF&Os>t=Cd)J0x%B8O8`PFAktMI;I7QC-_w9vEh-S6DH9pxS{+pS~I6+1)pt6$$xN zT5Re>P9K!u@dp;HZ~zT0Z2#sKo==<9fxkny<^Ss#k6j1BZsTY14oE zPKpRbBovOT{o72)uMxB7;)5k2J&85z#fy&b9R$T6)Bp7pb+LS9c}F$}lBP8Y;YP1m zfABqQVZ89EGzZmN{ZXrVpSMb;i4ZYj+M>aCO2@-KtP@4n3zQUh8rix6ja`IXefmIcTULxF;v%HCFppOEs@ z-pI4U2R&ZWaN)IXB{@&%+e(n2Y0yBP>)T`FIZ74es?3<2uA3NM?hsH2CP<8V%JL5C zjC6-53z&7|vl4Y{*Ie72l03hTJ3013-r;MG6)Fl~-G>Hd4od^QF3l#djHlIS_@9eUV@c5D5vTWiLyVVk=$M%u5p|(EzrTSy z(-g(~daa*L4CqS0YB6TXMJuJ%v)LJX6`G3nowMlbXr}rVb3!=eDoznN&E0l>^Kon;Dt#5u%D^hzf1TtHq*TzNzWRjsJ6>)b(#3x7K_fUYSX0@Svm>n^Plu%*DxrLwI5&T)U7ES zEDFrB4$Lk+Ob^|48Vgh0eVQHVXDaMbs7}eJQdTDgSF1*v^5oe?hK2@K%SgI2l_tJ* z@7{Gg_Ua_c<#aHMj9u>6%Q#oJrL}2Y?8-(5x`VpkJypQ$w;ZTR@}=@LCTQ1nG^UB( zq~|KCC7RLgAMOywU+G^f(npK*}`0N;-@@S zO_Wx$%OLK!Jq`2G?{8u5w#xS%|CGc`jnEuW3GIhzJpNnGXUHdaFNO>}D@T`S4x6j2 z1~oQ0UZN=WF`C{xcHUFAvt`6nGn_lhTT9+31rZq0A#s=b@%URa;;&3_y{dy+Qm^98 zZE55VQCyTlL99WU4P!vw=8N9SX&W^ALf^F-5103vXZz^@=de-d%EZim59jbuak7kV+_FL|#Z9(KG5wQDdgJ z?ZZDk@!?AOur=G4Z074G>@acRQ1yj+J9@6`q3Y`+QIeWhJ%>mcP16s?;wDYrYj(}N z$7UVjAdh=cnKsm}u)_qj4VX;o$`_VVbSgfs>{a16r4{0mY;qj-daGJd7scDURH~+6 z;eFbufX3ogCXU2I8W<^og>O3pkp=B=uR63K&e(S^XJOF7dy&zyc2X*pSS`N#G5vzN z0fE75uS07zuT%A7n0{QO`mR_Kn47shSB}=myXerCiO*8Z#JpZ{*`r>$0&x5_Zt^~Q zjK#y$Brs=U>=i@T@ce4bQN1-sFIS%7jz)X}_Iyohw@lvY*)j=<$mp^n;Yh3F+vjFR zCk$0{u4># z-`b4J-Wt>6s-ot1eAxJ~*sOg2!XfoF-WjXimJ=riPu~2}yC^@~Q*zax znpn-uC4PhY2FoscmynoEeo=&vqkMSvd7Av<_9X5>(uQ|F%ZXBk4SmdCG-C3HWT<&L zvNR^oJh%GO#9`9%2Z7Ninae^)Mbp~Ex^fz%N$%XIrH&fL9)EbQQ~w)VB%UOF{aRj` zac9_jT>C*X?=h+AfGd6$(@qEAc7s)Q@7J^X`o!Gr_wt!u7**$?@+FU{VI2dQogC=2 z)V8Kh{c@V%O|pp|r6*-b4{hs0%}RVZ{wdpec#Tm=s44PI9a@#wR-P!q_Rlt548=s; zeZOnKTdr4Gj(5GwT2UC)COX3xn?s2<_DnHf?Y<93_O*-D;UVqw%$GOUZ)tuePOZlQ zE4V?KXtltm-I*L>fX5GJ!r;XXoltY;cPc9pvS73<=Lz^odw1Ux9~oI!iYn6GL@nc+ zKw%8d&3Vgr@?}|CeHq^E@{0cjVB;db!+p-3SWm!9s$H;00hlP{_Xkg|50YQY&2Qu7 zP14`d{ge^ibw~3;zxddjAWVE>il21D`mpx^k2)`ffQ`0c-&1$?G_bR8m94D#er3-Y zwY|*X2x1lV{ZPk7-?Mbs_*l_9@%J+ivhPT|zw_4HBW)*_JcA241GipNf)yp zmN`~jXlpooQOb3q1qRl;`m}GMHwZkaz+~8#PY=_Zw67+W+unL|m~mY$q;O2V{I>TN zu{}`@IeqN3*n6CFTkQcJm5zXq!Y}WzA9i{SySpo9^v~U`T^+bKZ@HQ$e+)RZILdj- znrTe?4|BJjzWL=^b%>GEZn09ug`y3TpLP=frgiZ<$SQ@Efd3rdS8A1&p5EXodBPqj zVE|tpZ{G_@@oY&L{LDPrB54JjBdN0pLxk1St)i0wW1CPGS_XtbS6_{+wc1(v zos|2G_^%Rgf$Va&WVn5Kl#|Y^j&pf8XCe_v<-i3F|qR;a%pK}59sSh5{*SbjB z={JjGZM!(;8ozvtw9IDMFI@=UzK>_G_;?eeb!cDZF0)HxOdo4@jNMVb34k}g?^0#1Sb!sN?B&A+p7cqTs#1>dlj;pH8^p_VMQM-6C zT*=xRNX%F*U_Nma{0&rWdo2*E5cqrA@p6=UoTyUg3Sdf8FX%tNe)kBlWhEaE3Z?^N zpi&>q{5%vqG6JMAe>B0w5I*>OcJm^P9W>VpKV{o}TvD=cQ3y=t)^CSS7`nZ$SuGuz zZ{DF9FV#N8T)E42ZGp6R)$r`UU)*;O%SgbHXQ5?;jKEXUcOW2xZgyMv_J2p1A5l|P z-6O7VM+Z9R3#`kwbOeFVnvnqvNGegw_VluwAI?RMR}kqWKZapv?Ks#B=g|xLV@1Sj z;Qi4bbLiL8@;ig)_^v5x^KNN;Vd3D&>shy6hVDbLur2TNKFD^nc7?QSRq&U^WsvUN zs4J>yiVcchb6_)<_*Nv*BW?FSJI?s*fXbH_XrLxL9=w7@0~r=?aLE8w7EOC`q_iK8 zuo`A-J!0k&YFa5n(-)@rn@W1AT)>HZe7*-fQull~l03um!XwSj{XkWXD>)*hfjXH7 zA4BlKZGBi)l)4twam{+y9#j1iFIUj1s*vBXTgVhNO21h-@5gaks&OdX=>2YTjpD+J z@V`8P@94)p3uPoAJ64JtE+YY>!+GStKxd$&&#FG7w+evXbt>l-bo%i?=o+QO+d)M} zlN{lYx&0hjocg6p$h|<=r7Yb07ZE8u+?G~Gf-#Ec+3iv1_$0|O4ohHwS2z0&rKp9; z@h~Tzv1prq>^&>Ghx?xw^hB$_T;cv^TK?<$sWK9f`I$%5l#zhf8BQFtdZ*%e85(Gb z58506N?V&K7#bW{;xUZYLyp>a>9c>yk!E{@F(|kM9;~5%mDU<9CsqT$uNJGXJ<4Go z5SuA1A`I#CjP2nYxKjWK8gT0`m?gWk`z(;TjJ?FfAoI|teW@L(qJ>Q zG?QWb0!koLwQ#A2-x#zFa3~7f0JT`APx#l>TcDmO{n=b%mY3>paeaj8r@plTyGxFo zAOdlQuY_^JtpdYxZV3krBAXvGnhhskz$js$k8z(gwuZ>PN1yLQn13n`r~O96(ETy&`125dhbC~3OVUf1ytZsk3c?(z>J zhtL3np=eN3QL*kXE0D{>cI9YW0Xq)NkouT_7*oK!{*$^w7@xtqJqJP%W_Ho@$?deR z0z--6$6S{Xkq4Y>KwKX$t>^CFjS0ds4wfYR7LYizi&+9|`sL&n0$fVV;mSAlh>ZmN ztfaWKBIC-hM;9R`y35PONpH({Q~KjEn}2#t)y3zR$K3z^Soo{HlF8cg4mKusxkB6W zu=p~cMT*3*SB*F01L~6g^OdaD>ac#wgYXbOEAxY=Fp{p8K%pdw&%1w}=T8Xnce}0p zce{P@!ZOGJcKXyyh*{D^VM4@R&=GPI&7@g-C6mIdB;mzDk{Sr^>~D)=j#FN%5yxZ~ zzT{G>XAhja$05nTf%o8|huf}M{^amifqDHG85fws=}me^7(H9KeP7I%US;S@;2Y_K zbj@YoVw}Xe2Ho-)PMXBSzi)1DT=}il(dfJeA;))3gDB_D3PPBKTB43jYKS!kw&pZh!98HcXA|T=+7Fq~X_>dB4}Z-d4%hhotiUs$R~xU28(HXedp4q#X_cg;A8JOQ)x{<29r1RKaTOt4({30 z7@=J%wovr@-yH&xjxw`*GKs2LZ8=TQA_L3T@tv-eRvkt+{_)UUsY)UqXGU@r1P$|% z-^%>e4q$WH{?(XkBRDaKoLu^-SB3CnPlOMn9Z7NF0gt~Qi1XhMBtXMoNOmkxM@J{a zpPNh6XFyXrG{Dr&mok?YZQ-V)GxcF;4gGGj-RYh)FljI7@ndlbtssim8s_@@UX3)r z;E91j(H1p-nAj70j0Xb`Q7p90EAO=F4fj~nLQ`eByFZdbvKN#*3; z=m+O5zdM5*=l^a~bxspy+&$uIo=$22)QdP|6>e;8)wCEaj9U$7yvQI}wVA%8lf)+dos;T^BB` z1g1WWx+=IiEj%4o%4jYA*pfI>Xx!Law;kv`#>&5+@pyTdYGv!yW`PY}A3jw>u_4=Ch2uyfwuA(p@{y%>FI8-T{79PiHCoQtRs_Ld0iuq{nvJ&3|P z30QTFzlZeh!?b__nO?wmmi=)}NexEjxmFO@!vsrO+nZ5lL zF&Ljxpd;S6ggT$EzJ+M^sG?V(36NLL^&0ah2Bu z4{RDOy+8rYgyG^NDGA3rWs|{#Bt4UNYBt>?lZtCysULgD!)}Ldd=cJSd$Y;BH@c+P z)@4QL*_&L7ES{`b(o=AQ$4E$MY)+7qtE_z*6LVp&=zs2;Bre_1cpy89;jdI9%jj#F zz(Cq;eRyC}Pg-GvqndSlO(5^39`qM6hEq(=FXp41kr<7@t9`;UXuxb(%7WbG(5Zr@ zx>UU$#}L@D5^gzbZB#$n=hb9Ph6`hy^f&eRtqj?jDy~j-J>fdvIwKeCc z)$II7$$Uq!X9K^Iuk>ofyip4E3keA^Dc;xRD092pe(BStrn(~=vP=<>x^+1=ubH>G zN|K$b1@vO`an*50%NrRdz+cg{J4F4J04_9t@d=MR1LAF0;;e_}*?;U?oV~$Zw zCaFIHnB)sJC|Z1D#yO$h#l~XAdrW%lr)%^QYFjvY}`GzVujqHXT)PQ z{+$$FUS5&BF4ZB>-rC;%W+JVwgNm5I2LTdXKsrF8!sN|KumbipSbhHCT-J57^1&ILxN zH~=-(>)qM+(}e4Xy6s zZDfm918t;cAb`XJyrz<&>!HMreWhdDhw@R98~$)LJuxPhK7?>`kI!lT$BQ$=x>B`` zu~I5%!=OHeN$K=xiY%1JNf!DP+MYD5Oe{T1L)cR?wUdMWCuu)}E}f_7hWN?Ve>rE? zC;VuAM9DhX8gO^f`M0+=2sirm9l`(<$Po{BfoJ8$SCM%z=6c1m%VDoQN zLM<@RpsX3CyfK2tW5ap@T*3}4?eor97XP*dRiWF3{femAof9kcfW8i6Oof4U-5zjwenQS#XvC7OJ<%!74( zdXWv=mvDH)%2TP^W$spCWiUWn+)U!$croJ5tl`)8mI?c}w?c~Z*#N8J#2x3dYTs*V zwNXAXsy`B!Xwqd<-S2kiM$<0c56Ze-T+jD<|I5?ddagIEDAyq8k-*lTWoSXwf-9by zaohl%_(VQ%Q!&9(m|YpiHnKDhF-2GS)Dq=oNySTYdTd>k)ZY5*W`+ zw>fe_rH^m_o+2GYCcX9Xx)Md6Q_4aQjsQK!JS=xH0$ax4j!c; zhQQDVEIfokZ<)wB4L-i&o)uD`^lo*y#bUZ!wY_M0!%^;gU9hkj`+vKPXRqic7Z1^a z?tt9U^q^h0Eh}bF`$8)KNZ}U$`dH-~wJ2%=fYjoA=}r@>P<)LPMlllAjqj8lvlxA> z4q*+%j5!6?NB7y)LBxD8qBjc_+q&Hr4S++YY2Vw074fKM!-8KgtCX5Oo$?!x8hDs1F^QB}^G>S{>2 z@SlE(S^&B|Ii*G3LCJ%#kbVV_^cRQJ49G&x@XnAAlZfhA8NMhDh2ynbP=U0a>Ij8N z$%|0LPm%E$ucfG6+ldtkOn>M_T~2C~#!R7bRd9LAs(BU3T_MAn27s4OvMBT-b|+b_ zWF9;iR!t`$7h}b-lBT_^TPXHD)g|%$)NBF%T;UU6g8wvbfOeK$J>qr=A8Y|ftd)bB zWnCmIEUgFN@ND!&a^ry)O!weTXZvVz3RsW?>6-e;nBZ1$v=z9VSoSU1| zm&v(VY+Wj#KH+|Zum#tbhV|W4{|pv%)gswplEt~lyxe(Z!3A~A#Fvttoju%Uv#y`z z!Wq&3w5HrT8dg?WLa8#_%rhUtm)i|jN1@2DOqXI33)$4v1#p8%4r1J({Q=8wWc7y6 z_(~l|V0cdITIT8zV~sa~f7_JrN!Y}U$DJkhiTZ&6nq}3|H$7OYw`40XFAoWHbpvP~ zumLjam#)tq!5j_6^usIh4A$_Vls*F&+8Z1Qy84d@HGd)qQ@`kbaG){A zkfWgz`wGICI9uwJvqv>Cwr!1C#KOwm%xP+BLt)}nE(LI9qaIl(x3PaZVx6*u=z3$e zlA^SWcCp?3x~|D)e(z3Q66ykJ8ub8#>XLy}Yl;@nvMW|a@&Hts^lhAHFQRgjsz1)^ zvZMJ-7TxnDqF}9YRuZ;)7*s4Nfk*ZevK@|_%{wauK zA5#5nCXiQk3vKe=Xri;c_!1jNu}(6Wj7~6NPo$PO+js zXc2VC(k3Jh*6H0f4e{8-Om3Y4D3w6Sh*TlE1nfO+SEIoNi=|Sv7gSR?6iUSzeGuu4 z>izDj&Gc^pMQq0Cr&~rw)4g;xHcEqt+30oSYvB-o_7%4&FmGsiOIA-6Dsk9ID5pu> zVEa4#$~VHhV@4AOX3?FB8+js}+EkF|zWExC6%vtc&8`Xy15x?%zRI1_&T{9}ekz?+m^^)(&m+kCZugRx+rk~KCWf}aJ!o!*m&SXr)pxZmz&$|ev9Un^|Y4TmFlqxU+t{x zqDXL~Xvlz?i`GnYdeD3%arOB#>*U)jL9()tieSmt?W!*>E=y9KlDNf+WP;C?oBaO* zI()cRuP&Gt(yiAJ<8i za*I7$Qe^=ieE6;^RzRgJm#6b~#O1Hd+!!|V`g(2M zF;XZi_murac0sV3O+Fjahp2Y#))NHbkLg%^{}(D2IIGL*2FdK*Tkd2f7Om~}HXkBG z7Zo6J9q7dYEOLUY%mANog>`eCCAZ(iG;wkG;~t)%qV6ux+9A~6M#iEIhx$GrS8oij zGzkUouT3P3HiM2~8?4Wzs#YM2HHMmvhL5*5x9NT(aTa0EzMFzz%?75zZO&a`+sg`2 z<{*)C=Z9&YZHXkLzz4qn^}ls^UNlm6Jd-FI#!4SO%h!>@dBw=8^^~Hzv57o*U}5Z4 z>e{DYam9-%9P7)_#xTC^E2rI-;v%@>bGA}wYXCy+`m;Ug^38u*n+^gngj_uL_c`;4 zkrgc|t65%ryg-bTc6y9qFH5@5haQmdk=`&#;k}>wEgW~wY9Fx#r^hgPZnFsD;Ep1> zDTl+=GKHyKIYSz1ByS%7I|h5r2HqXL7IbfvSM2_}Jc}u(qh3qx$wKFbyN()8A9CWG zg4wohGlRM+;IQ3oo!}t3jreJ~k;*2LbTTiV;agzJ^vq#17yes?-of+L$s!0(>MlZj zL&<41jWOm6&0ShPZhPa_xP3o~p+vt^;-)|@uZPQhl~5g_(bNSgejj*WOE;>o+9#IS zF?*u$-We`uix$m_hT6Mgo}IBOxQL4oZd=|*(QelBM1o_}NRe-1E`--&(<{`pX8*A> zq&QWN`#?%UvIu#Od*aGExIL5Z1XSQyLLW4-j{4utf|A?ASsTMS2>ESiO^-4Lld&!s zsP0)to95^*f@KdV*p!xitLE5uWZp}~lVZioR1qvM0!aK!fsPvwMC6YTd5|%0*Ysi` zH@+za0dxXdCQ}~6YMMRsAi0uBXC|M`wBPAnWgKKSQ=E3OV4KitpXhuq%8)AnwgiY$ zTa00o4Q1h1FVlMc&C%sNE zNM>(0A2^5rlmAHcG~v;X6ppJz>HgwKo@bOFWm3*;LBM8iuR|g4*(ZEHZ`eddKV9J& zNYTfiSotj-z~{_nclKRoeH_-6tn4o!KVq*G=6d4=D+>ez^Fx9wK`U4T5Zblki&qJj z{WgRsS2l_9r!LA|Q_wrl$5#MF2%PkR8l(>Vk7(g0t$ke??2;y`m9(oOShY2F_MxL9 zAELe+Y5IL5!DQS-8wDjdK0pajXrs%2$Qm@^%Ws;HfyK8PTD$aGmG&p!Ss1-vvVuBc z!fclwJY zap;9pplVC<(oyN-8Fj2YM3X#5ljO=p2LATZhjs$9+r*z1Q#?~NI{=1+O#D~W7Llak zKP3labvR(q7{nd?3`?>l<5+`^&TANqCpL*NM+{ zf84}_D2Fs#mNxroB=@AL1h7EJCO&Y1MJ0P%e{c>IJ#$6mwc#(dIM$T;cq|>#KMDcn zbdlh52Dh8(VrJhqRRUro8(`e)ygGa#w*B4EvvX7~JeF>GS5lF(4D?|P`X3*L6L~z} z=m-m4?=IU9bs8kU6G~Q-yp@BLy8q$WepU{4 z94Ioj@?SyW&e+Od`93mu^WU}{VaNM*V?S~Kf2bG2P4~;#A!9H9E$=&=lRtypoxz>| z!q9dGcmB*&cjj*XTPzV0)}JP3XL9Jjp+|&0^;Z&)4F3Gz1NdJF_+J(He`Ny*SOtJF z1xG%{Z#@~n3{!!tKS9axbwwrApmmNDi2O{H2~_P6x{c2JLh#MuKfVtDe2$7BK8W>Gh2$!C@HR{iBpmF!)(37sX2gQ@yWq z0t;Vy*J)wrBIex>n>9#(6x9d_$j(72hM-gyej}NO7sBvCO|WlM$L~i4oWSfGQV1uY z;L@r*oZfW{sp=CPUazD6>jOlf;zDrM01C63=`a@Hw9?+vwuqpiLp8**_+Gq#}F8q)Gc9v8BEg~!1TZM?M5Dbz6 z7018M2$~%}wYRrJOUyL%y8x99Lh0eOn)csTSt<(+NhBU#(9iJS4?Npx%>wQVNR)ov zm)~Vm{JA{@sWol~37)zla$wdDRB{!X!_W)xk8_QnW1uHEuBSmk=4Ib??eY&TP|h`pd|T+ulLMUZYWO)1zdBcy!Hod<$N^tBRPz3y_PF|IemLxcK7?CT2C%y=a z#qYoGEh4Q1&rkk!J3ltM=ieIokM#m+3c}d~?ViAftP13~uxe-*L#zNW8~nbS7$7xs zkQ#x0sxwMJSm7%NM0Y1Wynu41_CHT|=R*hM`wDG6D#U<>39QfGR^_+dnYBYVMulc9 z^m=3*cyirE=|_9llg#hsdkM;9fLs#H}r0W_nAGvC8j?^7AZr_D*3vSh_2{ zOCMNs|9FLbc5`nZ7-a!x@qf9(>6}@FFc{Gli%Dn!1W`tqMS;9#1sF_#)&?QJfX)8@ zIpNzZXtTl7-r18HeQu!8SZH)tMT6 zY6AN1=>PsqZ$9Wogl;6@Tr1G7c~b#4N;OYr^Vx6eU%l6OptScfKA|sc_Feydh4-;o z^01v-zP4jV5uWqo9$r8tSogO$c}PNQ=X4d73#ZF9Qw;j{?c2I%j0cWg_)W(V7xuu2 z)1dvR%u6804Zch@ zetMApjzVYvsH2$V6W&h&Z=0bvP`GLka~b!vuMEKg!Z;sWOneBQ^jR7S)>v2Xtf&Q^ z_IkCo_+02abeQ}Ur>Uvw89h9Cdb+0&fa9ug>>khgmya-la~~Jh#h~XE|Z^+ zaE}}pjcla2YYWYrpu=kr@nr0Na-m&eGz;Z-u(JnE&TWJc!YdhhczFRSQf4oQ8nlC2 zDb>888<|`4rr_0_mUm77(>HlRL(Uq8M82CpYn_4x9%;VCidi(ivG;5G5|b0O&^l+* z)8zKqgs)ZcgAyuzy9<*STgNjwo&e2P4Zyzm#?UKOMb(ORcQCfl6*p2F zc!g+p;YvRUM&A^sBPJ(x-8`ja%OR8b@-ojwTUVnUhxXS&5cvAZsD!qG`m5^WrK5!e z`S^O6Vb{f*I1G5iEfC$<^ejx}_MLVFFKGBDf}oeyl0sYMHSz*v6a{2nR%ih2^rrQA0vbEk>62=Ad zHTv?*W0i*Col4$RN};;_NFXSN5BMg~f+`N^e7XDX6&RY%Ouf6y9lB+p21CQcUX(Z0 ztiQ^mV<5^pcXbScLvBML1WtK%Pfl*RpTigrB!XX=U&F-%HTo)lq_B%KthGMl^9v#> zA*ZgSzt`HUy0h5oTk*;DkMYzb@31N$azowb^A%*_;TYoqa981kK&B=PZ~NnUgZkr* zd-Kwj*7_Z1LkXpGQJ|>tLJ3NvByX`yFDE-wO^s}$5CsVYLf~VB2D!SfJ((Bh4))?A zc}%11=}fma)~YAEJz1`8{SI{P(El-N1&! zW1Ud5EUZeggSFs&(3p~3+hf4q_>spRjL*_kV>SxOeh8?TuVHL~5Ps;972%$AbJLl38TYVgxrSOu&qnhK2fEifZvK!Dh8 zA5C$&y5eT3w0BQnPfSlT1p&2~0-Dj}9Y`}YmVx8q737aX;gl>bTOM+yGO)Es0F*K>umr;TE-B^YibUGwW(kCgq86>EG4ki2cuJI^CLRD zf-Gq2!})uKfwK^7QMZB*2QJ9BLHptQ1uaKCDTKsX={aV;$4ADiecFxC6Y(Ct`BJik zku`Q_RjFag>mS52{rXO7KH`X>&_d0!0AvghG9;Kk9g#I%{*!OaOgdu((`aF*rMv)h2 zSa0080b0<&QF15n(gz{8fmc27@hZUjxmmD&xl+t50uc_Pr_N<;!9bbw1bo4IN(~nf zSeCK;7hBbB~b&hYU7{&ZyZ~i42FqwSXZNSvIlmZP) z5D=%bdS?g)cHq&1=-J93C!XVq!HYKNNN8~g3VJqt-D}pdvGWa;bj@n z#)g>bS_Oa9C;?)}zFrZiwbVnH#KB}afIH=K z9DF24&LSHZmv!9rqcn9$%%nt;MC6>`4!Lg#B&T|*>>ENDsrGWENQzBb9{-+v2O`;n zH+OHc{Jjb;$Lvm3OE2C4wQd8v_i6SUWa3KEwb?nTxUY&DYR+pTqs|ZuJtRdm0VKlm z)<(56bVe(70)4V@DaqKe5JU#*o?868g-1Y4Z&5Kg+ijBZdGdS6U5mE%M$t3JE-dB# zczwX}O&=OSUWdi{(@SxPZzg~R9~pz^6_O=eKo&T)scKO}S4rv&mX?o6CiIQOVNNCR|+bG(`9pZg)#o)`}Zf|@v#qRe}{8e=;Lpn zUsN`}6I%ODBFO2qYGyf(#Jf5CO)%qO@q3LHDStaoUax0;8La+(w6YDn0@}t%+;0bY z(efTeW5dJ35`>=duETT=^6)#Lo!lPaOcp?>}dEO;O#dMHE7gud#rS@Ys-?qM|Z2S{F9( ze|65GI>|FbNItCx9gV_;@}A7I?D-%;mbn`1Bg7#tim?SPAB`t{i~M4ucd@OeFZ^04 zoo%PP%=@f6Ln|PQ@Dr`(K(Ys$@1svOSP{mrUE1qLJiNMA7ItsQAGQq;?f@Ir&)bX# zkheCB_sWy(2VM7b8+Dzph3tBLiM7K=jvPpP8fKmQ{mixU8m#XO0Q)T zL{Z&9lnJgRA09DiGK7g3YlvngKnTXK8Y|`n>b?R`yCi~HAu?l9%6K1d|J_f#KJp?G zHgKhz0)~CLT-*dSG^t?#g2;Gkjvdg64g*An%Ns+_n z9-01m_@N7UquhzMIK0?MX}+}gCEq=;A+y}E&{Ih8OxA;*e4hmmE8byFUvu3}hyYqE z7Smlo@O;H!`Vg_eT&dtnZ|ruerSSlyEI=XO!Cc*5ywYE&Rdv*oL5Qc{E|ytJ5$$BJ z=KmOj>|@f0kDk7gndsW)2U?@x#pV*OA?VBz%KTno5 z3x$Gk#;W3!Vn~Ivnq>o*hV$Bo-qnb2UtVe0bn94zFMf*FhbEPkKHF$#Bt1%Bs_N)7 zucFkIB0%c%*@nSg&+ck!zDtr0hL(vYNE1?vamtyksqi!;fhUlh2LEFzeZQ6>`$Vs> zMzM__x#Q~BTVg-;I6)yf0sjP0xfMr=ZUVr0^f*uGJd^J#vR{O{o)yq|hllIa-3b6@ zVLbzbgx+ZhB8a&gp27gvdwfRKIY-HosPG&lp{sX11QDjzL}wS6#9#oX z@&@jHfVQ~V{IMwVE4uaYz-y%PB?whwMq(;jc~f-UxFKg2B-N`}w+km2wAGvgcj8fa z$#8O6I+9OkUEGTqNe#Z=%3=K>vcA@51Tj+RfHDt6Vp}v0?WoxSUU(lD20>9=v~!rR zZ!;_fsJ|hpP$_~c${l3Dl4eoJ{#s)Ehg$r3y z6!{G`^Cb#ZpR4--c<*^V@K-FC|J%2?{?u$=a^$xh-?5XJpRa~@fW_)a0A{Kb;!`IJM#1Cf6SR1%Lm*S)*bmz9&L* zSLPcLp_-~Ls6i~g`E0!eGVij6C_M!PHU06M#$@=`E&;8tFTlfWjFh=cgd8KfO;?xz zG(}Lc=gpbcvrSO`6hPiOnJtagSi@h6kVh)t{_$;ExfgcW2X~5~GeQVaAX4Of87|nA zgkXTk72NRYd-;Rl52<#z%_X>}H_}h><;TgssInNqdEJ@a0A_ZT89qSssd^IDGf+K)w!zO01+LR-xjU`Hflh^aCW z*`aaVDM$CAkPESZPME_yj!{b2F=XfJlrW0jke`Z@scD<=1K6o_lJ>`kA9@KfK0N~i z^6Yun_t*c!Wd;rBq_zT~wj~A}L$&+)7_3LHFNxd?@&Xqw!f`Cpww)Qwq9U&+#s7Gs zZMeCFFrry|h_JBTe@ zY8y@s?UbKy3{gpa%dS29RBfdpglM<`13K0ps|o8jpSQfIVmts6C!ee=-}~fqoMb2m z^6^qqftU!>?hGA(CIQ`egNXyU?mpZ#$nN-v z(T@JZ0k7Xz+p(VlAfB_3YBd-eeofHvMqUmR@5JoB8t{hUQ_3vR!}A6#=a>RKiR4zK znA#u~1UAbb;Wb6Cee5koTF<26RkVqxFD=7IX2@fG`KkfZNs5zFdA9EeK!7~?bQv{& z5ExX#kCNWGI_T=|#pFW9HX0AlS%F<#@zcEuW}>%{sX=5ESXgb*)G3Y{ga@V7YUh`# z-59UHyBxJlU8z*{VuQ||QZ^Wm)rV@4=Jn!| z5%@6nB#ZJAz-B4(i5+{01s=}yj)BDV=Uc|*zk&*)TNq@Ug6re4a0&3VGzF44cz1uZ zj0^IgrEytHh6C5(9=gDX!?i6y3N~TSkp{70W3=!#GR%$t26wmdfgOI-okq(-VlV+a zeIPMS9XqAF&sy;Q3v?bIs8_i000cncgu#&5K=NQjO2KRK9*zXU6P`MC zmrd(TdLf~H3Iyd_XK#24(^&w3eK4&J*`V%`8@>@C8?xeCkfK6N8mCuH0JzomL|n89 zCP!+9kXPpqCRfP#M-k9Rrx@!9d*edpCakWmf*A-?fwp-DM#kIig&j!BYwC7_ZYh3! z4D=?9um|!QA~Mz_rmw=`KDHpE0p|(;yYj>~UU~F+ui8giTH5ME&rkERpK;#aT!HdS zj_H8-o)gBU{q`{s^*y)86_!w5)~IGhHcMu zR^*sMQdi$HKU@_a`x$~B1JcCpDaXyZZ(l~I0NKT?VIs#qhMTko-W6O9pb&fQMt&fPM7+5)5=mQOySvGUGsh zI0>vaM7<+^G>`zCAD`ylyTjMJ({EOykb?ZU zWl=q+9p04!1~GX+_SFi^B5+xsh;!MT(y#~pBS9VE415!MFS@w&%>AW`@z#1(T1v|n4n6}5u!kY9T z--{FY9`GkWQUA+N4DSq0XgD%kFvTkCo16olL40wnDRnBdAGbN3*;F3`b4RSJKfD%c&he2WnP{~@)p<@H?r zqmgd=r2vod3Ms1Hy$Y&JVQ>5>0x=O=S2w`&dTQngScy~DLPglYDqfr2H*`^Tu zG#K+3bv^VxN^w<&xT3tA;;LCG#i?@t?u>q%yh+}-eS+_uDJ-i~*|jQFou&P#MQvt! zKdEmFJ|n&pAqTi67@)d_!;^OzHPBA$QnSS?>PoIMn$mY5lSRgJMD2UgB!o9w_+B&V z0^##|CAn$!x5G&qCH+oB6|;TM6;st&YT=ZTRuGHt)qpRgDrM?ItOS!ENV$%G{*%Ao z{kOl*1(P?L14Wg~;tWz@+TM-fJWUi_$r>eS03vkF;bj5H5|22HzIf=}6XmpUTTK;E z?g0-sH@8Zmce?c<=38R-zyVvCLtHd+`G#9aFf0g@^*C1Vh8(sh8@h%Pm|P&)22`Cz zIqM=L<6{s?h%8nd)4?0O^6J|z0sB$?E-w#-$GK(5Cn~s|UEXCg+s{%RSNz4~<12EAeaqnl8yFgapcV!^^D}57vTPJ}?aCUU9b4!?S`8S@v?T z0a(SWL&HO%eOtd?l>KOPjL4Yjx-cLIErDoFxCUy5k6Bp*&iG_FWe1LvFAtwt0MB1- z+!_h97XZ;tcnfPqlbRJfEH_38GS#BD)(z8}K!`B-2yWxBi*nAJ%ZjqxdW{hA**0*nd~4ttFjBCYXcZ;B5CCCZ9~5+XO?s~iQXQInfQ%mM9d!a35`x+} zv_3{y1q^zQsn1(oA; zIw^~Cd@ht&qwI+uQgLa|yodPn-FbTYQ3x0nbPgUo23nkZsiOeBlWc8N;G!3yn2&Ks-PbIO}#Nsu!<=VXu&e9zxow-38Mbb-GPaEH+PN zXc#^&f+8h9Zu{t<$@w)^!v)U8;@LDFnzSGtzMvr`VF=-bT@_40x(^%4HtACiICA>( zHRNju@_?|43mS%0P=l9&8^!eyqj%P(r*_4R9STPxUoG;gS5? z*0q_2fxfO}YdIAh;bgw7OjVkQRBN&03f!7SZ2%(}#k;p|>31Y`gW@n5AiakL)>H02 zOHj&if8%xMA^V$ zOfAS2DTJIJIEUrsK?32CLBb%77(lSyZXC;U$d^#+aL)j=y(yfBjgF44jhte?$Di~{ zKygQ}s4KvELQSOuO7w6b;Az&jyZA}+!dka|c#O4c6mXI0v;10Abq_BeuH@*se?N%1 z=FKhb+pm)zXZoYa7*&++9j7+zxEydsi)WK6a$tL60$;2~*7w1&WVRE}m1-U}yBOhU z+PTudXwHY!x*t7yw8}xmD+&oPptEEw%HKyvxgkTbp6Y0#AyVA{*n;kL zYefk<=9H9d!+)TMX|Ke=hJD;0rymJxNJvQZ(~7F9sma~DH@G)&4yGtU;~O&J2D&jo z*Ktcrv)8aZ+?qjO;yvm zfjqi$NAIc-H?3r{2Fvbjdvhl%{hrviyb#*kQY1OLBc(pt-=2iM?Fv4B;6{FFC2cU+ z7^m#L2lwxP1;vP~*RMPz#!V0qkOJa-4d<1e0R&K6ummRxFuMLNP_EAoyjRf7qaEw9XN79{((I z_I+gWt;2dLbNd%X!?X1_@lS-S3Z++6vS*spE$z7O1^>O9>dvOdHM~@+tW-Zrb+R)# z$2xhXD%jkP@6*A3%q#lcN$Bs-*@N<)v=cruR5-dpKzcO(pgZc|)AIpDMv@vskVrtE zz*zFV^4i)-aM5N@E9W4iP{}nB6%|Dg7w~l!t zx`z#6T+om5#L{99EOTaN25ik(Q$=_88PX4N`w0mT-d(xk?d_d*Q4j?!HgD@YI!Fh$ zW?;|ITmpYqL4*aJ!fwOER>|Hd|CtZVlUJ@?JtrH$IHL}CaJw-q_q|nU#+&S^jw-@8 z$|QJ>g^?y<>vf7E=1oJes21TRX~Wqm)n2Yn|9&q1;u$VDhzaVkplJ{ z92_USKg11JW4?UZ1N(r+z+=daJVN*yzqaPeVQXtE^GxeNvU$kn#c!9tyTH8cH1$of z$bzNu)?~##ha*H}jE9((mJT02(rMb~Lq;3;?fv7-RVI!4dJ3{z26EF(->6QwE7Ds_ zHTxdC!BjS}=hK=uxy~-(+@&Q+qEXYHc$?I7Sp3p@{_QWy74D=jPdPZee7Ua*ms&>j zrncD;cXg|ljEBvgh?WsYCoDUi$Z>z|MCsBCP@|F3$|bIwH*Y>rQCZAk{5CxeGtl-) zWWNS1fLX1ey`iC@u~FKg7VPPB(TRzaloTb6(;na@OW!S7A=?_oVviV@9gd-tj?RAK zQWhAmSkkh4;Ojx2d)L5q?Z@QI?sfMdy~1yfyQCgeZ+^-XRaKBT&mrZ3Vp2%>+^^h~ zFI3m{WlJkfCzCYUW%b*Bb9eR1%)EfT177b_4xZ(>u1|Lrb)KvD0}=hRrlgNv2C2)@ zo0Bz4GH2{BrWwXXUrWzd@+(v%{-#qyp;*>s`!Sq^Ew)Qh??ti_?_8|()wlE)adZcs z%CM%fqv)W)%F@DOD6*}i0|(Wcv|En1rKEIHuUS}dbZt$vF_4p!Z)Mzq>Lg(R`y9&x zL>c>$y%DMxE>fsA9aCSj;ecl?FX+qlDKD{rV;c068K*F@BMV!vavpyTCp!~Ra`tp0-K zO3*|U5Pj00_ubR1>M*76xsjO44o#N`SL?DKI>XgN7g9zAwu z1N3J4!xsEm1~EhGA~9tnbe#NH8XV#0m{9Q5m`Y&K~ERFcwg8X@a^;sqO5c(Mhe^U z29>kJI?RH5q&me0Cd`b6hHHO!litM8Y-5ROY*x$HyNY)i{AbSDKLPXRs2*O5NtMt{G zRrf9)?O~Jo4wYg0g5k3!_l;Vqp0Oa)ex@f~0vJ{Lbuz>yKdT8yWQZnm>(mDM_d2d} zVk{<|hTg_-zc^GDt_i)cOOscgU_F|S2%j2oc`J(ESi4RatM;l9ZhNS27cnJ2kKOoX zf2Xy^k9(5L%6qC92Ts4LwOXhA? zY({mhC7Y zbfV3)*?ZH^F6cYr+GxgH@YL^E)AcT()pD^1t1K-)y}dy4T;XZk{(Jt`S$L80UXA1U zmsoW=XQKuSDmTMCVpEFj>`6m~!_L^2y1cP23Lo~SH$KBkZ?G8dWSNuz?dgeRqq<_l zM_*nov)$`D$sE{3J3F|muN%GE``*LRn0ftVHQO*#Gh3ue^id5D+x$-g%l?M_YSU&SM`4%){e2 zl5ZYQ;_7p+po-PjPU5zm9DOu;hW1x(^3ypi?8PJm*16SL7~D_H((U%lZPJ>%d2bPM zN%0(u_xJu4>pdTJ)jGgp)_c=-UczAWrPN7*OzbHH`Ex=aP*H;uj zyc2Y(7&Xf-5UljcLSB88>O=wc!`P6jNAAHFv)QICE5|a+VY=jQv#U(ru?&NPchrkF zlMGkXyI;6HX&I&;KBbx3PiEMtR)po%OHVgHXHN6POFB|WT;RGjNJ`o*(x5LCt0$3A z5XYfYH@u(T{_2p^S1Q8;hSeYMPjEOVc6^&Sv@V!#8twe7JpVem&C{oIa`W;D3S@>E zUuZSZ+2JcgYM!={jfMp))0Q+e<9f4Vk0$l(rpcpM({8r~J-kmJn6P2Ty+ztjvm)8s zkb~tfd#v=xw1j;)u3F%ceX|U?x-P#HCnm$b>XBUAjnC6fGSd>r@`-k=3G)hb3CGu> z+iHY&OHeCApOfqMvqtn#GrmkN-NPSriihpe453_(q}%xGWjoeP<*$c|j0U(%BEw0^ zSau6D-nuNKnh@G^*kje9@H4fNr_Jg5I6Fz}?--^aHZQ7>?|~ta*#^~H zik5yBIub8aRG!57pJg>_sJ$K-8b^(6U~i1-LoI4fVhRy`S2#ph(|8sa19Im}=MQMtX~**$lHaH~yi-1Otqf(0dxhsb(dvdF=U=8-1x{o= zii14i>IHIVY#%X0$$6a4-G=g;l@s^DFy5Tm9Y_40p1iB9YU=<^f5LazgN`e!#2EcqyLmb zolfP+{b>hYqsE%fGHuBo4Sza1FB)(XpV28kdb^o}or6Q5$dOL{dAl~^!4v8QPPuAo zJUxG4WH&iUjQt>(1?}_V)?ANudp2z zBO>KI!tcC`nYt29z3=7A<+Gf+64{SeuAm2>9dLZgqMW@3LFh^pYR=%po!~N`UXzsjck?S@N|f`64$?Q0Yn z3(?7sTf{hrjue&X;wV9vDUD(=mdna-ua%iP3OiX&I}T0D<1Wp+O>b9>FHU0h25UA@8y8~5c0`r_op~GS;H;wd(nyeS z_9?@q<>fNoRn2K4Br5~y_3zV5RVqBP@xU@WjfUhoH{xx-^ic>17s~0^1u;I-+r$@e zsq?U6zmSZOQ&Ae|hphE^Vm~*0-Rkoo(rgGiW@=V8;EfG`W^Gnli~d?4GBkI$_Kf=C zxUOB|=MWsn8_AdRO1I5 zr98?Kvv{l?kGShpWJpsgKlyM=iWG9Ax3%?h$Nx(7j)(1sJVy)!VtqY>Sotb=P=4FZ zuUfdzuj3``2#p}qTy7IU61 z-v>1hn}`(qulb$Hds>D=9UqEM3};ZkZXrLYo8DUI`01*?Gz55aZbG!kZ87zP*+{n`AGs8n0e?CIjyyHY^$8=Acu^|yv`T^6|sBZGX z;B&^di+fsK6vmAyY||%8x5#UIkbX^fTIKDv>p4Q|QvA<SHYpX56$299@f_1M9ziS@+NxJhV@bn&)-wqMla;)2*5QAtr@&ycC(Z z`E+;_t?BoeYekK5o>Z<6CPoc>42$%9h6$&uT#0DmZWDvF^XbGU?v0W)8GZ4%oLYfp zREMMrH`l`-bfdM%RB7@TOU=_QDvhYj?-w0NjfwG#SfuOrwR^V| zpYC5SOM&;cvZ?SeL*NM}9d(L9yw(6yZdA}EwU<5_U+$Z|d7|`cu^g@h03E|*xeogE3Q$zc{ugFy&$i=z^h29lT8zq z>88tJ*)*|i!`4}1U492|kOK@9%G7&eNG)BUh*IC?exmXtcMq&Tzn>lDZL>430M&gv zYf@j+Ff@@%Ql;z>dmcy0Q@cC$e0S%v3c1_6*@IKl1VX#mP$wCUFn(HQo4SBJN9SA zx6O`x3jN%XL~%dV{?hKhpuZc7rR>jx?6ET*00hIF9%|>?fg+IbU@0P+DhEDGxRu0c z#Axc6N$p8mO`O*qtpJ#^;#GN9?!)VH6P;L{NlsnrRPCgrJx>j3+0P!n-8n~j2!m?oPV3T6I^!iO_muVu7W}L8XMVn}oT9L7Si2b@x z-)r_fG4y%dCU;P>I&lz-M$C!rGg2X(duD@)8dEkkr}<649V)8!@+4Yt*D)%wkamoo z%B=5eQ@Phmlh^jJ`lFy-$~dBK>pEMZse8OpoyOg-6r1V)f=Er~A;AqgHajXc`E)db zl5kT(`Fg5fuTEZUaTSL`uL`Iuz+{>COQ`kp=-N z36&5~q$LCfL_%6H7`nSbh8W^q1GZ^u8Y;8 zVmpnqG88;$wA*p!lZJ!WUK8woaKTfEb#!hV|I2?E>OvO26R+f&|lX6*_d?aY-)>l zV={!}uBXr0{>LqP3xJAAyKYaa>0nA-%j3n80)4@ug5!X3QQFiC*49uh;?WeChv%i~ zKpEnBCj>Fx@Jzr2bHrk*QEEKy{Q2`0Cu@<%+?RV&eXeKoudk=0N+xY~md~V6T{`P( zm-KWkwoW#OCKfwMHmkhwwJNOy?|&j4r$P8C7Ka7OdlVoZENy<4vk9 z3okC9!s)f}5-J-TAlc2KJFUA+{As|adtT<=Wx^_`YCOdP5cEP;K9djPf5$QYE;j-F zy_xg{e6Pg^)`xB4gZG~Wj8%ZI@e>il+~FZ@obWmIbDUW&dJX&%EcM70LuttuY&CT5 zD?GO%+F4dDm;w-#KLK%o+{KU4>ol72TNS;|4aaW*En-80)`~>bp5AjDIn}6=R#FAR z0i^wsMjun7_lx8@f;5x=k)vqXbXFGSeU5V@Qa!%?b*tflU>#5S8EiJb7<9NTcn+17 z&>K>=!Z^z>mpCF~%BL$Af|zG0(AvWl_SjX{G1oIYlGA#OB3EN}VLFt^h|XipYic1i z*#Y6NhN9wDewKUcv?O}Rz|&l$#fO@M4NX~C1A(on;*Dh+wpwd`(bH6Q*iTlWL5pah zG`RA9zX$qbSMXD2Op`ZjUKN1>AGSb8hqZCw3pHL|)xOvA62RI|-eh?rYiDMESbUDf z%jfA88%T;dDh5#OS%6|4a+!7O0kq*TOV6m3tLhOH$Q?bDshpMQs#-*pQ5=w3joh*Od0FKoE6>YICQ=V+vtpvn&(3ch4FtlnH*X#_JQxMcPCU% z&3v<}R?t2^P)bDNeXfoC^-5Aue(vbh(G4ZLI#d@*n3Vejmm2Tt>vsD#bfxWhviF_L zPnpZ$tOa`U(QKVZ^#sR^m;#z^c+DmB6R)$h>wft9!RmHC)2iY7=Rcg~ zeP&%ghkTQXZM4^2%=MF-l<`5SmgmlJ{B^$X>jAC6Rzs`oCqn7Jji}lSLj}aJD}DL3 zDE2`XDX2BKGfNHT5?I6e;6aQu9#S`p^saC2L}7|I<&yP!^AW4c=c<;7j8+T>*Ki${ zwo=#w5Ou*e_j1&z+Rweeg?b+Sh+RP8=3jlZ8)i(`rYe^>V_n11mR@{M zGRUxUT7#E$Zm669LQ>qOO^KC;V2y*+JF?%IVbNu558zY)kFQ}s-$M6>=5WSHS?QIq z+Y0s3T(RTmF9-~2#FOp&@5oEM@!^ULiz&;@iG8(**l1p(xm2kXHIdpNM>T5G8jh%w z(JY$%LfhVI7fpfDQIOwBzine@EDK0kugjdlsV zjowkb?my=^lF=N<-N9Rs_?g2~bLpf|cs=c5o%?*~Rp`)9pKoe+t#7kv_chlM*jF%B zl_>l1UvudyWayNkJEmQ7rEiz`=VGtl;vKI1#g&_P$Gs&Arr$m9dLiNVn8w?}3rw|V zB)09{vX-*NV}P2>G$x>rbrm3p*&&)UB^LVPiQeAE55)=*9T1Car&~3PV~UxNNa7cV zyCm(ZEQs3rF5XZc%qPp8gXb_8eOTb+nMz&grCX?x!J}img3P z%Y6*0Qu(cQwvliC;eEOwhtvab2IR&MM zoqPEuxwQG%HfzFOQw8QVm1N1XK6J|Z58J6tou32_Qm~xmkn1LMQmVRh2s? zo+iP)MJHgAJtxR;aJDgP=vm7aaRP$~S+MHU7VUhSu7%1C*y!N5tTmUOMuEP;!lUEy zm}u;62+F0ojn_`D9w1-4KvLWB(A+?TObmNy1_!qXVuEw&txjr8UP=4*(kD{toW7_B zUd&6ZgF5Bbh>C!bpnMysN$q^le|ZGK`?hD_52*l@9_HOIc3M7H>0M;ePOI)@9m=u0 zhRro+m=$_npjD&n7nsBTR&75^mYVA zo%p5?T-j#T2J|HpslnpJ`ph|*JMYeIFZ4Lgu5O)|(;UIuWIv0h)FrI1Wn((X#PikM z@_B^JKDEI<>0_90tE2<$#AWhHjY3r1T8G+Iv;dVjXldr>s9$=tuK%P`PEzY^19-u@ z>9v{d&;GU9&NVbLmsRal7%;wfqs3e-Hz9FKZsW2}-n7fw%+H-INYt3S1 z>jxM82%fS47H$2nD^NarB}PC_I+~l@L4-EeI3w1@q@`&Jl||1y^DUilP^aMDG&VVz zPJs%Iiv3<(xs`%2v$EZsSeGg7VB3=VNcg1v*%a=;y64Oo-dM?cE^%@SgX?q(JL}DuuJSAH(t`$~+)!2Pn9qxVL>2n153=*Jf8Gc`2vZ&Q3w!-Jf07^BaoL?cGEd z7)x(sesaQZ;IdYTsCnI&X*j#_6ioHuIFqNCVLu(uVd)gUU!1Uw&ik2a>KljOcBQD$Z?cvI1$A?Oa^5hL7!&DI zWw1{YC0C$!q7h7{o@`Bs9W0r7(qyP>A;yYKvYSGQT#gm{)}+JvV;kkN*I~ANghh9E zdz-G5pR!%v7+ivn1@>N)*nfZE`LWZuX#{U!JVBvag&HY>-uc=YrdlA>{=S^Yme^%+ z_-jQRpTU^0xkt;m(Q>G2`U9uUSq3qL(zcshn|g~sw#CU;~E zzJ!`^M7kr*!~E3px8~2lAzl&sFD)%);B420a=5$rreb*Xs;*mqwE(_(&lek$-WPDmb=;YW;Iw9_`27 zf+5PdK$qPIy-4!hi~RgyQl-teq5c2s>^RN`p1e-D<_SPx4#my}KM6~+yE=I%w50>= ztWJN$yS<)MKYOP>c>qv2Ngk#p4^_K+b@_{c#CM4eoE!^@jO69fu^*uzjbHz4 zuts0PIMuhN%9{?E{>ziw$>m5S5+DsvYr~-}#)*k@wN2QogGbqbgg(T|{Bbm@=8R!= z-&83*gmJ3&ji(!k>K6v;VGYPtDsJi@Uk$x$b0X1yHnpT!G#SQMpg{AzwafIDp<*FI_XZY0=RZ0){?Qmg~OxO@!6ti4u1fC8Gw!hxG#rp83d}8IXT?6Nt&g-1m{OVUb6xcrq_Z`ln znf7Pw=9gSvJIq|CH9z$>T$ttyri(;RdT=1=it*G30s~ykNkONSe#|#DMQ7W|mB@1~ zwM|eD1`x1^AxP&nK9&fe49ng1spBwHCZ%;l$s01ypf0Z~UHq_GSY zNaHZ1MKrS`mw-UyuQck-ykwfyX{?u&tn>ELys=JYiPxv|FAMapel_9J(CikKPaM$~ zX?X<&#mk||w*Fk!jG*95VYImSG|3>hzMdJXY&I8~rdWJB6dIOsW6gvQSfHUV<~ju} zA`>Z@ao4$W>t+f4%61xBT54GR9T#7=(FEDJ%}dlhp3!4vz*?f)$Pn~+j_y_L}FVjU+&&zB|1^3L$$qY9nxh^na z_`4JCs!a<(e_(M_b1$MTXCZmOa`8H+B>+Bn4^X;i^1?Q5Mf39V>gDd)RFNj~nm_q! z77I8$EuD$T8_s)~yrlz2Aa{5bjm_9>_v8a}bcI$suQPo0-4rXbFfr^feVy>Nri?p^ z6{WG$U9Qy~nHPpwo&KUV@!caqD2s@lD4xo!>%%fl!)XUo(Qs#)zC15t#V>^(7*ccf z6j?B9g~eMd^Ctmruk@bhZAP$ow9I|q$h!GxdsFsG=#7Dj`m>gVuI)^$T+yTXHX9P& z*aM@dr{q%3EOk>_j*ei)cwM6|_|m*_QYGWQibsOioOzG>9```KT*}R_?u$BF)iMzW z(Armih5csa5pBYT6B>qq8>iG2Byyz)LzgPS_AEiJ+u6~OI(S*tN4xLldZo%p*|Nb8 z6f#Or>DD(g(t2&_mX{AVs8dvg33E%kl%(?tNkVfok_Jpt0GSQfmH!wdL4@}clhr%=0dUubB7FnQY0z+D2-PUJbz0kP$kh~znJhf*pEfE)M zcsnc{l&vI&hN(g0GLJ&jW#VOK$6V(yr=~L_B3|VgowwxX!9W?cwd6QB5Fe|zb;gf~ z(rEES1Eit6P@GS^PAT{Pi!APV;Vu;!I5B<<-b0zrR zfSltivF?osH@UrhGTm2b0gqbeY4QHIHyiQ1hOdUFmWGTgw%@$@jNJ3$KIq>h=e&eg zNIYx%x##iJ)T%L3pXyuH&Xp$S50unHbf9{tQ&^lAd3i?A%yYO&ydr?b!`LWUj1=oQ zTzQ22%e>0vQQ(!#+^erbx^l|Z>I-6!r4P=0mV00Xx9s6Sttr|s_c%LC6ZwBAD zx@098U@CRV?AtR{dFL0QM5QC&AGfy34B;v9q1V($NN?p1cwq?&(Qm%Hh2DW0Gl^9; zdSwARIYJLQwHUbW7AF>chV(&JmY?+KrFr4U%uh1UvM%cN{RJdl3>d^G#Bc(JdWfYMd? z%6?kkEV1ZEeNjX&1{B%!UR+T;m3*!{#eCv>5tVCb1A*4^gPLebt+lwpuF5&f_g$%}e6X_g%Nw^C-N7v2LKJDkh*!WwqBa1p)kZyT;IE{Ws+q>yebF^&5B|7Ff9G0@I~7nT(?s$uknH z|KfGL?OEgbiON;#caGrFg*p%bN?^)rbt=O-1i#2gvGuMq8A`@N?8qmSv>1ql7TU7p zoxGCx^$`_A?LMlF90~g~F*`=f%DS^q^jmf{-_Lz9!53aBe%XXaYobcRaa~Eobff86 zr8kNbT>uQ_<{$A8u0C*K&ZP2q_YU%NQ8^p=ez}FcR-(F+VlJE8ZI!QNfb!8GOhSvw z?FIgbJQ{zp&2fN6SZD?%YP+dTuroUli0IwMi}7e#ruo!IadB~OIn?rO&bJei$dGbZ zliuWaGBMn7b9~?J0@u^HP7eUp?`k*Z8%jHYy3n+?2~tnj)h@S+XXi09joqmps)V^K ziINs`oa+(vc$0ZAnu7^5Y|q69|D(qDWs!=!7y7$?j%1NiuKUL;yb6E)=CAgFR3uB5 z!9R+@Q}EX+XMckd*bmLel6vKuFJse3-&2l^4A-B+U%w&yeK^<;Rl~wcuZ@%Uxl<=T zkc6GS1AqPf$Zy;b`=OjzAo_1^13o70!C$Q@_!IknJMV94BKAoH|03_gpD2l-A^f+r z{1Em@=ziny*bfc*NB#Hz=Xh8oytX%BqaxCE`3%evz`7AOwrTkDVL_Nf=>pn5J{}%+ z<)iEJ2y6y)b9XN=$UmGxy8j52y#*`(<1s@|3kpUvf3QzUNx7m9^i|G+ygPU9oUi~( z-G3SRTpXmkS8;x`u(2@5k-yH7?i27XH(znEo;ic}EIlo)`V3KUTpZ2czITZEknI(@ zeG1DyLhv7>;Pc$xh=8w#vCl(I6e4UifBf42{ja331mjfIKyeTBL%N&t$Lgo?_pPt4 zIQ#MjAufX=`M(*?$+NdYhHmGceD&(p%UcmqzIOlD<2}P-?frxbIY&^!z=wox-mMUZ zNs%rWa3QR1I+e+ZL9ICbA#)zY~1mZt|CvDWLC$blTa_xD?8 zNBP&Dqo;~Cjwmo4{U|6h@^ZI-L+8@rHt0BT|H~yXIGai?>NJgecVN!EGg}21sjiIH zQLnk&mVu*a=QAVjIG4C|{HHUMaG1p%e$&4u7Cy|u2iGcS2HMD6lT53N;n6(FxduxP zhoslG0DBlY+eiO_qH8SR{eycG9(IhTkARZ=FZc9N+w&3@AmY^E2B-XFHuF)90&IcB z28?LxFmw)_)Z_DW->K+oZQg`N3;}ftYJKk&D)!L7IH?q1DiHaz$HzUVdeimNE`wYA z!**w($@~W(DnF*A6%I>&*i~^gpGV5%FaLu5bQ>sKbCVP6>Q)$6{6hw>r=V1Q4RlW) z`KP!1z z9qXgM-7qp5gOSNkH8o*Iwy*^Vc+E8_HUxl0aB~Qtjk8JP9tDrbK|(1(PFodpBR>~r zAXu>6?Y|dVkzeu#j(iW&7ncONSlX=mi_d0uRKt5y;lYN0^RW1| zj`ZHS&dyG~juI?kB({|si`Og2Uk;fqT+N^&9y&>@IL85R<0v*HEs>G|>AvU^8X*tR zvo)6eMv09V4jlzohL4P|epCaq1T?G5dVoXTlp#!*hYAcNQV25qi-&^{QqgSn$is)X z2#Cfey%WbJPIfh4`C(^^J|_a(F|VV9Oyg9T2{qCigQ10sLw&&|?%~2=E#hKXf)Qu^jS|n68 zB0zKo28M^_EdIm|rehU|@l)LR0OB@b8y(e>H->)CMyXfV-!NkresZc&++Y5vKkJKb zr=dgKQ9PxLeYusGz|{SsJS_U1A(SVNrS`B5E(w|KW$uT-URNcw+RTz86yDoo(|(;z z;W|JCi`a3bZd|?hKI!W#!^tHrY&H$dW{h|9i_5VW!ob~rz75zwMv>j}%Bkf*+hY_L zP$hd$QE%6mb?zurK$3uU?vd=|wa5tnf^FxH#mxSW|_jcGQ#ikabmT&R$MF2dxjb+yB!{8a~L9hw&R}#jBD5;$IGEbU%{k8UxI2 zJb&NY(h^LBdG--bs`02YDU+DyD}#xSFot7P7yAnYj_29}*qLt$)c=vp_LJTc_p0=x zAQeSy5ua=_R(x$K;>jZxNr82(-yWx?o`q|Qoqqa#ci5=MR@>MkPe^2{)p z>#Z=hASWl+_wVj3Uu1S=o5)%X1kR(+_wFbj@7;NiaG#qiQ^z8wW=jp$@{04fM-g3E8~f%D=eifm0{LY50s?`*m+_l{zXUuh?N`pfEhk{Invk zSthaC?PV0TBJlC3Kx>I`TPA$qoJ{oLRA_=cWQ<^;{+;p86bPd$5#ul8>T|&9o7%k? zl#Ca$nb^!*z1-lX%!vkCWV3~^N!>A*`JYkaPj%$ze(+yvTZz8TGb$F?5-eYVV7d$F zHEmO3XL^hG-a7|C0BbE2^=#)b%f?#!WIC8@g3_F%aIQDU$?&Uhybv-fQ@b$8byH6Y z+@uILYTk=-V<$P_+WdamS)<877-P6U6`v4WEjXClf+~?Kf01esRa<~RYzm$6Q$HXy zvQHye&%vCaE4KxOb4R6--`kZD_T=$#mR)cP+v!95CQd-Ea zj7K?G9aC=jPu9R0hKL6S@F=~QlbMgH9bDGYXmH)6kzroSr_iC{B;UK*?Kk^R)&(>6 zm1$*fu1|L_r*eIquI76FP#HX>FNo|lub z?fKHS^;)F3JGySx0nla`TaKkxSdH_yxU+J&4*A&nAvRlyrp55T^WUv6QGKg{t6(J0 zV9{z3M8#J7RSg~q=0i3w7%s?~RuoJD=FTT+9I)<;PdzM-C7oaWIo+ZS~3OM%TN%-Y^cHEPwy+73Z_8$BCvN1z4i6hL|sBy~7c`Vf4 zvb$iG&YDb;D2|}W?dt0Cwr7nbRr){euQ%D&tED;}Z8oaA3WJzMhhf?_~UARIN^h4|z&R8jU zIBHBdZ!&?8Njz7N@XyGn@{2K_)sU?DOFg|+J(-V zz~QMa;8_s|I)|3g!9($2AF8-`0v^gSf>2qiDG*_!D{{dLU!9rPpHy5%A{hus%+Dn) zvW1o8=R9VnG$d+an))h3wBaN7@0-~$0wqQNv5pxg@EXYTAuFIX4a{GJuE)q1p%Vq} zRJx&%w1oXLX?bnq%V>Y-@L^a+agyYOy?$;lY^H$aaj+@^`S2kQ56}KGpAU{lpY@kH zG{EF|!wDAkvIm0053lh6pr^HZGq=zEfzdO!$A{bK+CHMzVWMhY%p@6?B}zHH*>Ub$ zt9{&~(g^tSdEa8$6EihUO+jFV;BeWcMDlqG4KWj*MNmt5mIE&q*MI_&ttQK3TPJ&&41;zH5ta!-XeYj_ePvS(t{zDT zwIc%-VcWib=&#{Z0+{kw(9r0C%~fg1ZXoC*ENip`OJ-p461Alf5940x@MAwepMtJj zT}=&*^8>p?tYm3ny(Qi;4hp~`3bsH_{m{N$d(&%vtz7u|qx?mn_K#fn^!als%p3yW zja!GQ$k5veSVmd)?XT_{(87=CA+UmslpqY@u;lU%P@s3nt8Oqy zpVxPtflkbRYHm6eDb0tQ@he~R@eZiQ6@-%T>WRk%4@0LpBIIw3i$^hr!sff`Gf88x zjsn&m5g$1c2Y3){OYROXG;XE>@WtB3rd?M&5H?tRO!@f}_P-^El|6|9b6#7~VP|+W zMN30-CFMB?bTiZK?x(vQJ%dO_M?1=}y2xePH zM@L&*8a{*CrQ@&~UGldF1^PMERBUReR!J-i`)(J60tbCmlSZ@4{Sk;)Vcn3d>B3N{TO3xXCu%pE>57@w*<>cj&2+SphI|?pNP6W9vu%KB_$$bm zMZ;p!a^rAt^L-+<4PbU>XC%(oWr3R3VOCo(EWS7d~g9R4d*9d7rE* z`H`%4MjJ&8)C%{iu&#K2tR)uc+#b;U;L*FmaQuRLO~_tH#{%i)n_hFhK!2BLI-)Th zsESj(la((#UL9uanW;U?dEMXn9n@U9;v$;aX8LwPG#GxF=oU}sDo5*3RUGSM&sEd% zveKY`QY^uo^4rpxJ!<~2bSYerC=vu=p#fH0pe+Cx=Hp;bIkyZr!3!}L5?zy6G75@A z$pugu!ZQ!%>EmB0EG&Ek4~>O5Tk7j?O*n1Y9={sdg3wojs%3lVD$i)Lr5O75h_t1e zQAobs1kXKwz%>aUzzgS6e{*qAXRU$Gl^Z+6N6Dc=_UjmaPxoMtp*b67^>fMYD%FG4 za6dXFiKfh@ZoxME7N40_91(&HA%7rSZTOpi#^#jtp!tXyq_RuLQktnu214ZVfY;gG z8e&L@w{#{&(&cl-tTr$T>Q4WzK{oPP55QZx(4-{oyFRcvruvv){AIU-(r>U^N8hhz zuIIgsmh>4uKC&Nd^{{+qNJB0m87{*Qva%Df9iSF=b%HcPbwXM|dipqQB(h(kb6`Jz z>5?UtHIbqG&Ra%UNgv&zT%a^B$=U{dp z)r{HQ@lZSqW3`db7#1$ZFkij{C@$x!ZaBxyuI&E%6*t)I?gJ_W9mc~=aDwktGMEOF zVJs3#cvsO?nE8T{J)Z34c&LH{1p&2q^GC@CT{MCIMAy(eNg674%Nf8Z0V=ehxtfgH zEt{o>3nNkmDKHy6-V{R-9LpWYNrf%)$_&)=s~f$BAiJ!fZ~&72NVMO%D{~2nu0Uxv zl+Z}kNS|HySXPdt$Db@<{Txi~8cL0&>Fno}Lk$@R(486RI~EOVC&2nuVzpPi-rLLm zrQcw;C!B7}3rSrq=^EHP0;db&Fo(N3zlwQo@MfR2Kb^g2doQdA4QT2zY*y$0=b5s>uLI1Haqh$u2K-fI%Qvlj|kLV7!6)<0R z#Q}Q+`GKqI4<02 zuqA;CLL&%Ln67&$EXRq7nFc;b+S@~M%I`hft)w<5VEGZ)ktEDN zytl^45FRee*8vge38BXEH_f)|v&F zVZQgCmm(VOGn2Q>g@yRW(3$gxXdo{y|B{oJ_XKPZVit4e%xD`{VZfs3OUlYY zFv&2)ZMD*<`?9*iuX$OyFA=T`Fxp`!!x^g2Y3jj0pjiM!1xiau$L^(KI|ibzd%NNr zF*Qikr<6tLWTkPx)tke>>?V+w+|C)!YS`>QI|pq=Q=y&@XLD1U-6M^z5j1dNfi6TN z;&~!5FjFyyqO|sL3o3SVLNqvX0{s zDBQL;__EuQVVT;ebGyt3#!wLX^y$#hP_<^t5wT8Trx}XGrw)CvVX-A%0Q?ig(D#t| zz+y?&cNzqcDu7nj5hfIDRn~`;C?SFV!s_xeHdBCIwM83y4zAt0De!5qPwvwh!F}I9 z4Hz|Wx<1bL@Mi1v-B<^ZXPEMa_d;KiLcWV*JwXagjG27%o3)JaB^c(pGlP&2kjjp3-zlxLx^TIsF55&q5 z5e%O7@xmCtW`2+cgZ+l)1Vk&+}x@1H5*9cPau# zvfCpq`}tRk*dY#|0)*7ZJM19ihWnQT_Ybm+w}qy#-PU$@?|Oc&GY)-JHF%|W+1aj= z6n4(e^+J4@OFFRnCNdJQv5G^$MyCYO(dHfa%|L$jwh$^Ltmwq zDFLRmRfXE^$ooy(b!V6>1SjnIFNo25TMX1tVp$rS1dm??HOxLuYuX2xKY~~N7)vWA zCZ_h25ugs1?+df0lVOZU)Jp0te7ZK`t>wKW?q2KSDs^-ggRL=|v7~c-MXsAES%5yn z$|TeY;YrBEAu|1)$Ob6ab<`>B6(;DuVk6{-ADTg7bzx{d=ALRtm#G$L(5bBW3;~JcX>R>;|*= z<;z2d)PK%(M=4}!mMnmB;U@DM_ROy6N=r+_22F}ZpEEyx{D4+_bQ2U{Rj{2K#6WM| zx`mZ!o$gGxwzl3VHV=V)jmG`g-S6F_05XH?(h%+i*m3J~lZp!RJ6!B$(N2*HP-Zb+ zT!jtn@L|IVRpz+ZVDOu*LIUD)0@%EroR4((3NUfVSKPvtC>W^(#mQW#-CYO z0?PIJkO+Dzb+y|1dfx)f`r;!x4h{~j*J9XU|F2A(jnkKYzfS8w1Uwd-^-)q%e}@8o zSlX;(X=LQMv*iT-@njYw0(_>ha9jzJ;c?~H0%zn;*ntZ>41-}U+rt8v#Q`@Dk6i8Y zn98rP`ITAHtA&Xrvx5zrqWvqqXPcMd0DK5NUs8*N%#TB}m~)Wd62&Xb$^q+zC+lE? zps6OjG-)-SEqF!nE?O}zk_xcKJWShRVhaP3E~?p~sSF4s7iEbIWRm>w5~ZS}sD zJmNdB#Sqq7g0ujrIEi|ZHVpE~PK__Jpe0s;a$*bThqVs%IjDOuU$i>|tUm6eqso(T~Ow$swl(#FTdZH&|r zp(X+nVN(T`&GS!|Q#I+#0YdP_%w83Cw#_yR(e2-Elce={khD5-SAZFCY>h@(!3itk z+Y*u)va_?nSRFtB_JEaw>Ns-lPiD`|LxkySTQ%z=sD*NLD(Mbl(tn<7^snd2LBY!K z?%LYNHtS1EpG6l1zyc--0CyShW!pD{5BB3taeRiZgHey(zZdkN2NkQ2&bKZw|H1uI zB+San8Vc?P07t*W1<>|?b6g8S2wK>RJk$$2-`QlhTEN^wLqc4AVq>YHh*sr@=;?sP zb+A4Zt3f}oGd4OgQ9tkkUQu(;_aoAQJHULF7Q{xIg{ zS`8({6&4C`Zjt^3)dj4PpEhz5}T|Y>r z@{7dJoVga4Rswg^jtb1@a^f?5HekN)kUU}fNO z&ow$BYli>*RH6Acva%tS?d_!hZI2ay*<(E_7UW)KB(N%b#p6)g zg0#XYJ%qe!3am#&#T86~<;?jnjEvNXrFo+C;o`MGFM8i1&?e|Cv-o(b|Di5bZWpbIEJl~wwPXvB92`8bH)`XwHBXy z*gEU!>3QYKLx6vD*_}{T-je-??gjNdxdL-|A><#<;H#Z2!#sp>EE*9<^ZSH*Me>BQ zhpcpYEvS0Xm9XUaoyL3DkEM2_qE0ildnK@LJ#Ck?2=R_WJML#pbTlOu)x^R=8?~%D ztW_r>BAS-Xq$nfy!G0X8ZjlJU&~Hjr_!Q86Eh?huLEaDKmQ`_p?e`$v<_et3 zHL$>VN18WRtgmB#YW-2|2;A(5KE(dHGkm!C_LH!^JiV-M(N31W0)1S`9l4}taxMEG>4^aE4DbNKUwUq6kB zsh3P=uc)jvSEBs4;_^^SqN z-oE?B00@pnna9Q!@Ua#>_9b1Naa;J>FGB-y@@UlYN>vgP5>OaudhNcxAm_i>k)vJy zOfJw$*TVyYT_Uy<*!*0?;dLHvIq*g`>Ua$*lOo4DnOfV@yz`a56C3KPV zkIRJf@*ZT?dU~fMnNN8l5b&ntCm+6t$o9&{Pq}JuSB#4Qhqnka?mXpY8BpA|1kM7y zLiAG^1SOpK6+&djpBMk3_b#l7?{Mc)t%E*ba8M8&I58ysKZ##;l$Msl{d^LG2Zq%& z_V52b&kf!MXqAkc;+-)yodltuj-z@qLBYX0J3G^2*Hf35ma2n};o#^QY4EJ64EgFl zTv_Tr@RJ8`>7i9`zM(&)c9O&=fN?((B(Dcbvbms!;fIvG_#7mgC@CnuJOJZXyzLgd z#g2W^ZX4FWT;xN5LyEcHf?iJ19UE|l=QTY*SKKTj3lub@wvP%~_1xe%J_btDBrohJ?@8DJoj{$6C5q7#gaa;!MdupK@` zgV{yc*=}cN2Nwkg=lBe$Mx-|AS;4VA3|TyNU`Y61deHkqF7BdS@9u!E!1R+Z=t>Av z!PlwJ`Sg_Iy6Am~9-`D0)6X&xS|64JQ8hXE)nZ*Fcnl03WwV48-_Nq)PL z+LV+t8pJq?>YzY!diFR*?^-a9<-^?r8^igyPV(z9o^_$$mz01A$II{I9;>ZF)90mM z&A+|U+cg5=r1~&`p=pn20;*O2js-J zy`dJ6f7{HbSB)d__Ufa8vNBxpNBg-de$_|Nrq-8{XIP)k9IXcplW85MQ* z>{$p8i<6T=V8>9Mf^cMf`2CdmgqRpAM#f6p;cMtZh!i$`rOU9LzvxbickenBWKdCJ zd(C1}Ng((Eh!ha@nV6XsW~&1rh0v=6TnaX&GcYo4D(PGVmt1_fQv}D|kkDl-hkYl} z4O}8vob4h?{i3=)$SFz%<78`}Adtf+ z0Y~nKh#B3})W@oFb+@+8FE=nx-mNVI-56jzd~R%f^|c8*?*qDq?H@n-SiS*@JyfO8 z4F1HtjzafWdbog0%X=?PZIB;@_K4GX6Q$@R38Du{NWy>>TL#$7k9e@IcxZOkW1&w( zl=$0ixI-M#FJ4Gm^<4DwWZmB0Uctt(haM0rQ`W?PK%tbK%?Xxg8x8>~RR)A4^4s|% zA|kMt_DJ;`m4ySxKeXX>^&k<%=|s0SHk$v)ruBZjqy6=0Bn-#sv|A1$wfBl`KraBo z?V>#QZIC*Gw=_LgfO^#i7Si_2ak_IHw1q~_iuNEun*NPa>Jv3i4;XaH>F1W6&J*AsS*2K{&Xl zlz!mNaR>(44ua(2D?P|LGW17TPZn|Uq&BwMs=XbaT$_#*0*@Onib2d&wX&iDbs_iL zJLsFjS*B{1je_=>P30O@(%mrdQkvVk@2m!)iQoy!a|ZDw0J?+(2d5@vX3oDgZCCxc zdj<$qpQ)~>VBMeSuULHBIpXbGScb@%gri6>SGu^cAV2tkt`$fdODaC#|Dx|l|GEYr zU5@>_2A{14LDD0*A2l?jCwlv7WTY}kcIsSqo7N|UZ*!1GPyEzF0 zd#Xs*>j0(I_7ZY979p-*zwXz)XOO!Ns`WG7g{+u+eV`JHc1#-rlkD0vp;mOlJ{h-? zlG1p8OYA#Upk%xx$9XdcZ%{)rHb>)q(0(NLdgFTF1Gf#c5AtOEr@hD)_^Iv8mLRIo zO>2DU(4i%1&|v{YZ|~LZUKaov(P(tV6Z3DG9Uxv(_dF!zI4LQqy@+kXC97IGzYT#4 z$jyla0L}viSrw8Uot@7cA1j-u2lSdMPcjA?`uV3gI6%-yDUFXyxBYGEd~cztg8mzA z2pv4yA6Tf2OiT)xuUlpbKmt;(hW`dCkB9Fe!Pm2(lYB`I+zVh&WjNp*H-f|YDX#he zMA`Pk6TAJ|jLN&cH1}MHPt}t3Gy91;FAf+2eCh7X%1XqwYuCD^T;7|1eWL2M(#YEM zh_V;~F|?g14o+BzjV+KWS_7o=Kw0tvRXeobIZN%wkCnxBb#(BFiD_mJ*M~DJ^t3uF z_pU<_0+HL1R1n#`=jmCYRt*w$d6HO?FC@)D1aL=2PIw%%yHf;xNuBzQt*y>uio_79 zvaiX@w?WRh*^#JLYOAlLM3#iFr+E7G>7Ple?z&`Jh9o#RwzN7b{BlF@taK+p<7zA%{wu9GJ5_j% zZgQZg>ne2LrMCn$aXg>V3YZ*cJ5^9nP`k@^)~kfR?C9ciL5qd9Zxf&!kHum#APUXR zS)#9Pe1Xu+CHs8=)Yrz=n7z02h41O~#-1)&&@M8I5M~bw2-sK)199Ci`uneevky>* z#H6H6JFPM3`6MRFvf^Y;fFK(r3u!htLQSBhriArt z%*u}QJ(ML2f*jb3tp-0%!~31itLa~J&9OY|(5FE%F8h$S}24XGR8sFzx^nkbq$(%&wAq@bOK|_zj5rSXzvt;unCV8?|`xuy+3vprGQ7CBu>cu5V^JK8^37L4y>O z8BB!00j}pI1Av6Y%A*I7R`P_`&1RsvFs$<;q4HnIJjkHrxC2r=wjA3U+;!mk8fG3Wb`d#fM&` ztdWrs$Yc6-_h3cpAVxs)?kJH7xEM~1383rZ?mveJts~0IJ+58DOTy7}gyCecwGC#gKjN5X{6@4mSpkrIhJ|I zNb1xLdP{Vxyhaqyg#fx>^RA|t#{rb}vhHR$&wLB?>`0sgFC!%{|9bVRAl9-OAj<;A z-1bvBR}YWTGbFss1Ox<-nx@&@4K)Epa-h-*K`Q@rzc?bcO%Chc@4o(GKrXvRa? zGHlr{&mjACr^GpTBC?(I+~iH{{B>`0_&4dkp&+H_9QtIAn~V9pQgd@z-@UG3D-F2U_aq$h!+^- z^WXH3IGH2sFZ=9P6~r_yud1? z1-;@)A0Wy;8kY9zRZ!Rx_-2CKl!R?JXEOs91F<{3KP;}}Z!EK1Y9}(nMpzy>xpFw2 zK@Exh;D6p8YF7fRsrF8{8X)}ay~0{>dfeP8FJ9aSHQh!y)v|Yb!6c*g{=GVCPcq_UdSx0rnehy0~36m_93hct0J;qupoKhPe19J!0o_^Pe$3HFncl zceM(iI5GD`h5g$G7ScL0&r{G2(ZVLnN5LDiYIAyzCb(De!xaynp23i_rS`M43pciL z%ge7l_h+ZL9po!Oo<8XiRt-MZrR)jzQ-QT+Jw(z)2q9s&sGOS$a3ux#`OA+9;H(CV zMq%xt&D*V;hb;CKMfG6Vei|MXbq>TO2@LF)qMUQU9XkQ~he!p1kOAuY zR?cs)uMXPm5z@_Q0U%Q;L%^CGv_x|Q4lz77R@M3pU_^#V4uG6si04{C&4N-XUvT^i zjh~-i-hd|jb3I8hC{&_Tv4DTZC1T(4CX(VT-AQx9tByBdZrO(;LWd-5YlG`9-}a)& zcJ|!-Jj5Bzw4d)MB_T~s*#WXd1qB81Y9*@Ust$!fK@TiBy#Qtfk?aFfj`s*ci`=&N zvHa7BC~U5HpFh+ZEP^?&rUo8n1do4f-@h`gOCZ1kN?y|=0=EC{B7zGP z0K9;XQP0;0goF%7s)}YIHYG+dPy90$8^@8b3yt~4lJ_njKRmqW;EDT~Nby+M3CjGF z89J&0piKH%e#`jVorphv`v}N!cVn?M6*U_Nfm;v~dRaEraOT{(fqvE3usxN~Ultt! zu}vVGe%Tk^%s#F?yLa!mc?LiO_)(?IYXw`kZiTeu`IiapL<^V?!N&w(+!B&#FL=eo zoPm=VoMP_gXoW?1)vpl?RD?DDe!A9A1mL_nyK8tyb040A!Bi zh&>C-+aT0-*?ODjyp|S3N6aWKo0EiFTHs9~cmPxrt@<)bRZXp-sfj{zs@TJ{JlQsG z1l{+92S4#87`Yf)h2>LY^6MFp3W5y5+iaZ&Am)&GaWA?OWS0vFlW_5*%h+d;%GJ3* zzC_OLZoP(Y)!p3OK;+K9ym#+(LzIw-v2i&>X0*sMH#_Zjby)Qa7hZ$0<@d%vQIT;O zdxw^=Za+#qU9UI*F>$RMSTS%Z_g`=17Zo{lWwQy)FD}A@OKx%)MBa}T#K|-=-o(se zwu`g?og3Am-rrMl-h!qUlHg;1AQ~K3mmRYlW+1rxtc}R^1RKX?Qw>{Rt>|x0@e2wb zDceDIQ4dC(diRq%y}M$M{Ue?FD4d zu%Ty9K0#QR1rWWz!G#RYVvN@Xjfw$6d>2c4H-reVRoN|{{>Q7GQ&QSu^pr#&^X&Mw z50K6rXGN&(V`Myw>w785Bj>en6sQy|8kUsj>k1+<@f1cDc$H~JoWV~-mTZZY6cWmU z!Wrd#U+iI@>)SC3FAxL>3Q~f7{Z`U&E3V7mugR2990(LpKW&bH+Tn;kGi4iWbP4b! zV7{tUvp~Xp;nIN=m#GAs=H^C((1y?ULiCP3hoD|N*mjG~S+Mi-?*`(53>)z`znj$v zqydmw7GG9cSfFHw5Pv^v>+L;|$vTyB_wLqw8({dq{3a}B*d;iY7a#8?`L0|bD4RkV zhHf7h8pYu?#wBB{Yiw)`?^0j3>th=zF$A__9>Qi&!jn@$JOru&^62g51xsvSrKJTU z3Fw<`yrqoq9~~5{MR`a=pgG-N#v$Vp&|ETpUg-J7MT!EPvD@bvKbZk|`$)8h&B3Pladz|X@{$#HL!)PpCjE`rGd$Y8_>08=;c z^XH3zdA#AI&$@+xxA^VT}zQO+TkpOnyIcU(qM5xya2%uPEE;Q z`*0t2Slhu*Ld5=7(*a5u^-EpGL8*ZQtn*byQwaW7FJHEn)~jl0gaif}vrY4G! zS5~0rHEit;XmFeMaY{bmS?|wyc6+qWm1@rXVK2tbgfFwBs6ER;SCeQ-lu1ik_2 zX37VEQLfAWHZpSV7Xkm79C&qEU;h9D!w1^;RS;^;aqV-5}u-&LNwckkLmYs)A?KN`-r zwaE81g`o${lZOL+wGj0+cAEls01EiwppH3P15qaig!?^C_!je}Dh4Zv01r=iL~zR;4Yv zW5r)uP66J7n_oP}&%bNu&SdYq=h}c8gxCj+6iLhychg0GUD(js8v~ zV!RfQfp43l)OJ7-+?O?_)PvAn1oh)q;*hqm1N+INqOPT-^)JU1RbDO!?J&OYc>cnF z-48(vp;WC@&*~(=3PIpCi}kOTRj)ABogt47f%~NlD*y#=M%IqYwf-Mcei$FFpR^ z$1LqKMhB*d<#9~RLF6$mu5b9dr*LynK$QZH0eV^dSp}$eA2{_-nX-nH4Gai?o-2Hf zLF?D|_n_2pj1|Em0WC45NmeJp2Z7S3yhL1`>fREOxdUXE;%oQ>1cC{N00+YJIls!% zZ00_G<{&%pqoXi$Ac5oBE=9#kI6oPxcW5Ye0L}Yc@p3`t71TMlStD|-A%SY;3gk|{ z&2Dx%2*YpR{ymG;{v;-5d}^wRHs_4KzP_WQXu>wcOXS6ANSiDj47u^){;nUfe5Rg< zhm-*ogtsbywp$_v^lGzb!9IQd#k&1WBvC_71%!^2cLl0`!eAS*%ofBSK$5t)y1tYR zJ6CY^{Z>$-PBnXSHnK7i?0F(YYw&(-G7TF%q7aVfdJ@NC;juyzU_w!;O*OIPahKn3k1q3Lxrf% zgRE&O^UaeWCBV78fLjRB%`!Zzd%L#wJVg~Lk&0Ug{o!-}@~L-n-Mc7^)NI6Gj1*#6 zfS*4`)O<&O^vOf+?n@xYphvo+fLefsf?|Vc5hO;3dc%wQyViKtVK#GN6^yJ()S1>SpOq@d~LfB$wdSL0ov}qn5dIh~8H7!_5fNu<>h+*t6^v-u^GzcfwJp80 zs_IPEMt(k6DMLfu>(^hTrlxB9KzW1AZm=8w6uDm_t@Fl zy)ar%&dw+wpXDtPJE^Im2q)PYk*rMt^GkW!fa9Rf1xwz#ZCiCco;J1B$YjT?~7xylKI zVS8y2I7}kg|IDjENDB)KyDaa`liiINae%tx>mmabt%AfRD8rGmPdSzAzaJdpKpogv zlF~)W5pi&c=#C5z19ERImHlD7MLM_Le*f~-tJ0E^*8ZFYY82bXkyI(@z3OL0JQD@v z_STFq{d(8;rKPmr%o^g+bwYRED(Cs)varZRCqw5 zmU4Bh(oXjYhaeyDw~yC}e)8m!siGbSyq%|~r%Nj0>V6<^z~s3UWu6Cqg7RV+fZfAQ zJODLqJ_>?(!l55o-(TDVLZ=7}tnYG}9#juNMClR?j{+NBxkhB^7h0qPWL>l!F){(N zN#U3w6bGE-tVT)hb0e_MW1qR4T;jxSfO-A}cOa65iQ}!1!{b+qQK##eA$)J?m0V&m1k*5ve!095$9Kat>_p9x- z1G4dNFK+l#t7v^9-!-Gh&uuv+4gwEsOe*=B#e@6zKl8zN=dy~5isC;*;jG;6qH;=z zik2O(Qc*wNno2eOo#ra5fq?;}^<4Nex1z;YSP@sFKpmMtwXYnLk@14Zj*mn5{EkQ% zsEA|jWfIi?9Z?|CEhu`Du6GlJ19IptZHTKEL1eC!OCU$a#+HH1Z?)WtnBT|Bs!l|% zqW2&QY`TQ=ZN4%aAY6w2LJ`c5BCix$+uzE7%jpROvP z2Gmx>aYYIaDWpVwHWYjMm*JvRmm!Xq1L~y|F4c+!u*tf8hF|T7hi*(LdZIu#y>e!# zD0u~%I=}D0f%9$yAVFZ;u?29a@Ho)_zdsH(SNoQW?T0!T$hxekBa&Z1ZUGn~icMnE zRt(WF>77P!}k zsY18S;Fj>oXiDMbuR#am-D7HM>OO#8@3R?Jh*K2xpo7Gej*H2FR|3=#6DP_{Bise>2*ifM~M zT&)8}4YZe1uTeT0{lWe87Q}6y-eSkY(kWTSCEwr+ zeX0wQPHk;1fWhY(85vGrWGQHA3u5Ftqw)!gmo&FCprM_9%!Z_QLs|qHq&H`#d%=U!3Up)zsY{e&Wpyf2PY?y0aHO$Wj9} zsu>bkl32^YEN!pB* zG!N(&Q1btwVi668m(D<)XQq@3{aZUHElQnp5PYWVGKn;+^*RUu62;cQ7+2#4LrPT^ zxS~JHWZ%uz%ENon3G6lb!!E9GAJp;Y4Sqg*kqsaj7{oPIs9o~{RLjfzCFR&-B=cX@ zyd$0SXRyq_|G5u!FckY%KMzi}2>*ZmpUOZF4Mf`Uz?vm%gA)WAnNknk^?*_-Pbs|j zFIkjpC2r4^`_F}8d6PbX#8B=&eJ>ClP^;`X(s&SRmFFgv;esDgs>tvCS-$zJRH8K$ z9-RI2>YxgQA}T=BzC3${vr?EM4?o%n{;VVYUC1u${}g}@QSdU9AEi+K|KpEB`eqn@ z6bh-*{>LBXDneb>*0zG8^BfvV8Tn@Cz@G)#zbTOfX+P7Svi%1YRTw1YVL&RRs1Hdy z;REOo!~D>{J;r&`Gi8eAzns+P6Lr7*;{S{P^dcKbCZrPLPu~23*9NRip%4ETDGddJ zl44?jarj>a2J%%DfuY1;zy0E075x9rw#fLBhM6f+3MFg*^N&8<{SAKfAIkWD|51pZ z8lgP`8s1c)s^8b-ul|wmLqDo$w+M#)Wj;Tn>(X+6n*KM3j~ux^(f)eNmMxcGsn0Gg zEzQrDxlJ8{jt!D47yP5mo|bF&@><~V|I0HBdH?_VPi-Zjb08b{md{FQY&oz^j%xSh zl;~&6xko$#+~#`|H8Cz4*(!8uyD8q~|Lz{Db`)@%HMD5jhn@6o&t?_38Q5s{<`6b1 zxs<3jx7H$@gIRwV&NVQE-};BB-KK;5%XSGpT`m1juR~EHUoYl|FO`VwC-0MSA*h#? z58KRtk_|rH)qwqWvyEtHy6UGWRlj|wS?}kwn>QBF8jc!Yx>xC+ZA?(SX}p2lA*Drj ztu1A23!6bE#N}xTpQEed1otKb9aaxvHaRr6&S*o~NF@~Y{`dQ|<=f|*;0c}4G#Vx- zSF+||2aUiUG|C9=yOvjzbn!&9`y1oi-RFOP&zY&!1eDbPK~yNwfj9HDPKI70vaqUh zY(>;l#%!+voz(P{q#^&oKHMeYQ;iY;C;XvSeKVPV#Z{={a`ycs5NaIAMNEIbXX+aA#KQ26e|_wV1of`WDBu*{7e{;#eF{rJ+5 zFQXUD#{2*k$9AEe@(JM@L3(FwZ!TR@F|@Ok?36q;aH1Cz5Cec~)3xcSwl110x@^Sf zD6h~wJE=c#C`J3OWvK65Ag`*a&0>miGIX#t>@oSH1LkET!8qKBlcS(014_F<@!l)rEUhOzVo>1N^>GhN;AO(p)W=R+ZVM|i_YHQyZ4{StiGQ2C zxX-S^iH@v(VqtXUMBj8=j)MLCgc_})PLghS&y|dkTrq1*xzd@CW%6FU-(1*9uk*qe z@1k>tDQ7a)mPQK~RO~s=`$I@Zl5SS+{U+&uX>NFQ3V%W#mZ2W)>6LXrhc~Fl^*urj zY86{z%}PFTk}z1yRRiVIyiYIHa8wSavgkHNq#Zs#tPFi?zuTQm*$d3;9xsP8R_EXJ zSIR$_c)D$*Hqwc^h>)Q-qvSa4&Ea##`ZMutqZ|bz&hZPToV%ehJ2DxTGG!wo5;Gd- zzR3ByuR!7DNMqI?Bf)n+X{9^TG_Op`rNUU(G%7P-f0~$)8ygeNuis$yegCTG{l1o^ zr(px@vgF6cb^9=aw}qMqBa$jNw1rRaW|zle0#&-G)DLA|2aSSeBs4{PA#y+iaSM~9 zS_BwbS$%-C{PSa=xG`JPWVoKX!0z<%e!5d&|-g^Tje((WHeDwJrHQM?))ijKX5pBEi>F%O4KHV3|VBviJyprlfB z&vlj<_TpQY#!Azt$iu4AAaJTb6|U-QVCb`t%p4;Idr`~kTwPj>_dYr*PDr;yIKUc;V_gVRl1 zPJ7I{lc#JJe?|vp>#e;+izuOIN0=vT3l~td?WIZf+o2N#kDi&#LJp?Ff$Z1~J$Ju) z#!RVg4<8MV%DeSWH)d!JYMLIBF_UYF4JoU-^o5vEd8GVWs>=P*_CfeEcKL*3ici8d zWN8%^7y6${vjIa_ z_K%W>cw%9yznOB^LyaU^bDi^_m$N4CX>l0nJ@eMdZs+r}k>@z^3c-ntnPP;2?b*+Jly{3Syu1|rga(Ft5V*1 zPckg0uz4>Sy594X#Oy@KPjm(6%#Gp{EeUt}8x~894J*(NgRP53oh=qQ0=IetTm@cn z;3{P#&6A8Drn8mq+=K@sNl>Z5)?3K~=jl*zu1zCC8LJ1_TJmAbNYU_ny%+y>oE&c_ zIpa}1LVK~CT{@zWOm8Z}Wn9UK`hI_S;>+R&UL%hnAHO_i9@aK7Cx5Yw7+ag*1C^Bl zl#rHU8bv);7FHD<-?vsymARshlMl4;X&#v3>iYg7segK+z3|(|#Rq9CNoh$wwqJ_Z zd!l`#E%|RQDPhxM72e>-Fjojq8ZH=lFOGF4`^x9Prn2teIK9-;<2+kpsd=W<3iJ9h zd5d}qH-$pTc?dp5>na;u?@Xf=4)i06zTKwji6TMe*GX1=( zREXKD)7X~j%E}!*IuMl)pfVO`*D~tPb>nv8c6!#WIz}B_KTXQLJ*o_BqN7jF(FV3~dk@ZjBD)Jc z9nF{9AY3lDHD4waUTfp9J{7IoY91yb!$fv)_ZVM=d6@05K3eSI7(Aw!8onC;(WK0U z&Il{>Xm(o2;akhw6Yj#LUf0||FFX;q`SLI)AVf1k?ggL{%o8}vn@=Rx`ux6L{uDCy zk;8ALt_IJcUiZ4V&`c~vP9w=G-)nror%cK@FmN#t0zdPsLg(DOS5fRSv92dC3Kn|a z;L)a&;d-PN}tvTN?wtRR5b7VOfI2OB-(x9mu06*Dtf z2;H{f`LeNtx*yZ_Sfk0P+)(hU;;q{J0K8gPRV*~1CT@EeXwg#noq%NY-3LyDD>gfgI0<`NZ;~aOvGesI*S)?(#|7*suL z&_u+|3nnN!dgC1iD{dcaC04|Ea^9FsxuUk((}K2r|Dr$cw6>;Qc*FU2EAq$<0=5iQ zS6{tOfDsDvubd>=N}rT$d{4|>6tHYe5VcnNS`4PHtvP}$QXEkIfYL(z3#{&p4Z)f{ zxGYkphx9w0{n`tU&guF}Z%BUQy_csm`?7y@#X?fli{!L2W)4)cn;mh!f#uR1SeJQK zw@z<-yG6;lRn3x3C|`9BaK+E#$MyK*gDq`3-bg#8$2m3GUis3pMwLG-Q^?^lGo@#8 z5=XHGpTgwS&v9hx@y(8T<e)FIX%9LQ(9y7mU4?MYWx0e<@(_lQvzm2-W7{#u^gD|o`qLv zPOQuYH<7kiT2uO4cxf(g_^)|lIw#H&M@gun7E@1>^a;Zp`>8jT9FM6fOq+*?$HGKM zLxm0u$pEI<2r>YhQDOke|1HYhVJ|%sqD&sk)NuJcwR4!ltcSTmb%mw7WV8cmL${mQ zq~0^!L#9G=&hroUfkZK$M7>y!qbuD+GvqnNeVyr-)&S$^+CTT!vcnzr*7IE(Rxzy1 z%5KUK5!%Tbo#d>a`0|W9c4=M>Y z-bZ`xb`o%fkJwOnztw`GqESBun~$&NEjQjc%X%KBK<>X^v_(crRad|fayugYsp_AS|Ahdi_ECniXL!Wg`Y zZ}~(`Jcvn~_X9`iA>o@Ucga0;2HB+WU```1?SOQ9*4LK9W=$ZG_=lAoh2BqXRqS&I zpk~b6zYNzX_s-@-^OVPElkSVg?1c+dUe;BrQLmr+2PhtJ9$wYV`wD(~P(-RAE$(FY zMxo0Jqou4wvv=-5zaQImO%4M-CO}G2OQWY`y6B&`Ez{2Hbt>`ft04|;4>=_9HLM_j zrM;hM5!ncUsJ}O!QS2EbDS`Q$9vty7SFP$l3u<45;ui+5v)+ zLFI1~?N<_&^F2-IaxC`}Otby4?vpEDRpL(WzhTog{&eZYWw*MvYDx@MAi2h@(o=-< z36Cw;@Hqn=OrJ+I&w;0T9Hsu>GWNSj4&jw#%zietc<(08tfYyr`Y^N}o= z6ey$CI572m=!OPK+MjC4{cTZ~?`OT-R{F|?>;As$JH1`!UeSsDHm6{dUCitJqZuh2 z@6dhI*sDSO3hhm@un{9q*LU^3&ki#s$t3ZFN_f4vaL=Dz_k?wMl3ar2YSqm_H?YJm z_x}vPsL3vA9Vn3q+w$5dT8_XBj<^AG=_fDUs zseYmIRWoN|08|9uRSetBHkLA4ar+ruTOT_quAGhMrX{*^iJ0M{DLvpfDn9F1h(_g@ zcK4UGQ9%=*tpRV12+u=n@`PU+C%U;o@tosLt<|StV*mLLF;NHp8QWwT zgp#gAW6m3w#$>+nVT;v}DA+aZo zwGrwu8CGnW#6O?0JE(@;ZUL90INtm6o(C;;OdnCG)K`67J{;mwpSf17JZqWn^1$Tc z#21G>C{p#v;MBdy18=|&7!-WU$TD1A=w~KeZG}+1?x+!|CuxT#H`A3u4zJigXo+K4~Y7#>Wz*xRt{(x=)d_vwm$R2qeaxnw79Rd)+P}icVC6!Blqql)=S>I(oK}d z_=%Iw?n7(Z8?qejT#aU`#;$S_G z57fOFpgYtD0uIdjM&uOvUJy?A!s_*HEfDoV%OxCvxDJd_#A@V~_YR(iuYgc=+yTI@ zVuN*`b=bFj51N6Z8H#H6MV5D@b3~1<%grL?EJ+>Rn=Zgl7_&z z=vFo@d0`#!(}T#&M}R?sa6xX@XH*>*f?E{giIa5wvj>N{G1lK0L!h5MaP z#NFtxu(2z&-dkEZZ$jX#MJAe>uB&6q9BRT##Lks05!Sz-w!l1U&Q3wkJj+jY>P~Yp z&D6%~l{ZeUlWiip_9>||f=x1SiWP^+?;!rBSFDHYBj2W2Z^j#M(I|%->~xhlrw>-N zp4<&}A8kBC5^hNzfPU4nfD_2)mvZdwvMOai?31#)uiwHXnP(&2Kf~;(vs6i+|L9Ps zT4vEx=%bxUK5o=S)xh#CM$s};pA{o`idFn7HhTZ#+-+1j1h7_8F8q$c%NZ=d-M$*4 zGx$FGBrSGe?W&nSI>+MS-++~Fyk#QFRPLjGg>RYhNsMlCvzf0sWb znL_KQoi`=x6Ogfbc@}IKbgI^FH>(ORa50`Dt4t(@d@F!p*SCGTiMrCt)E%q0lpq=$ z@&$-r%xq(uR;@FA8yJnK3%(`+B#_XZmG?U-x$81|{(eb6D-dsNEMqQzEOy?mS z7th&v`>|~B@$+8HB1dK%YTW&V$)lk#S3NrE>?8VKOpDyw9~@pA30?S-fSV8jG#sfEu3Eipid|X~ zq9F}ES~amQ%3(thTF;fAD$fX;Mmxmm&6vj4Go8$mWaTudaG_56@P{VLd;cm~tb{|S zPR+|qIh5CWQf}S}JLFtp63yrrGQNJ6ZK!T?YHF%FM^0P^TjZQZTWcH+a1USIBJQWn zx&mkDGxp7YLeHZnk)T!?jB5o!f1ePS9vjepTZ&6RY|Ag)bBx3l>n&SiTPiS0@kUXvzq_H!1O2`5q zt^Kf;Z(fy7Bof`KlTG^+96hS0gd_klQsa*M$R@D_%jE^1)ea{&`q$ z1%FFq+eBnp<%7U~Jsb1Dn}zI0ok`u0b^>}XUbD`ZSi&^OgkJYOBX}H69e2vX(JIs7 zwp;?AZ4+cH7h)BiIcx!xp21~Z&zOISx~lze%^tMUTtyqNq22v{<}m)TddY% z1}_eDoL~-eoAg==wG^rBHfH_ON^FuUx<@NYxOyaX;`|hq^{l~a*--7C;WTyDBU%y8 z$Fdv6Sk7~vnp zplvN5_UwDc4fc#@0B!~sm|7$+v$wQJ3!*hf)!BqwIuy^@lbJ@?d2q}v3e&u7SWX9} zK#M|DncMg~|3mFLd@YgAV)F=ZF4b&gU?j~ZjQMGipu2jwop;tKx*8i{iCjq#Li^M@ zd_Xv+gra8#rqq_{_8q^OEA{4ZmJ#Nir94o2!%A}kyaG(5x_!fV6ZxZ*Ce$@@M9GXX zkKnr9R$MGtA+-@wc*&9BLR4F`EwhAqO+CKE$vB~Oj{o1xPk&Fbc<1}~N>XB^CD|VC z6t{**C-ih^#--F(Qbk2Ir?kz}(GE{lIG~A;Y)s=R+yblhD zn9d!Jb!fsvTO(iUgQhX>i%c6abUzXFZv?$U#mZi~X7zPw(aVyakE)9_8xh&XESuIy zBZBdf;fael_-2ay#m{iA#-_7eab5kDTmsMdU=q@|U!(g|W#^gc2!U!2j8^4)$kiMM zli^wBICKuGv{3E0@R#+ga;3}p)b-;Du?nq6Mx7oR>za-w5x4#xB*l$#Nx3dSE@Yp^YC`v1j55aAU8~)UBAQ4G7&XgW@d#3@p zqviSYh1TvlZ%E)l=ZvKXk-~q)i>g2R@HUH9%oe3x6*5Q#?tXXF-fJ?E7g)-dZlxIe zEx1eo96LV&vr9@GjWUgG)6pWHpNMQS33Bd{>PY&$;p%84eZ&5FrmXO!$p=jCmCef~ zqsT|n9){UUwp=dsCn+t)PA)FHckgbjtxYz6$O)xKJHnPt{u-G z-a=1Oh)1I8S4VPKXWnO*7wcxlmK-E6KZa<}6y-Mmc&kN=>3#?e(xZfn%=;_a! z8{aSAg6i*bMtaX20<3PX6#*1=aA#qioghg#nqn_M7^VL~Z-R_p@b@E5exS1N_n2if+ zFzQ4V1)aw;c70_G$THnMm|bQ_c~zqVneV4}u^rzRfZFnz!-;pR(0jW}R4np~Cy~~- zRHKF0G#hj!h$d9K*?YPymi2Hh9wQ~pPCnf$U+K;9H^Z2agh`3_Cah-d=?0VTEGk52 zZyDN^xj3TPBQ>kf-(NTP$FDKv(ED}PB>8IAAG?xmoZ*(x>pC$VP{&1hcrH>x$|8r0Zm&mnic>=tF)PCzLO-&02O0**qg2SuY>Q8c2I7@3jvdPfP zU|=|@m8c@>J9=XF$+Y6y_ZTU|53n~)?jPv+Bx7=9@f+>Y(A_t<`d7 zNM7>d+@*|6mC|6YGOG{S7K0|T92rKzYwpnvS4CChlBxSjU4xTSj=4ns)O%ro>4u`=Sk%dC$<+00qCYfJU*cwb$OI~YycJeW; z{H01^zY2r4XuO>T1h90xVu7d&OHuo#RJ3XGW|*f8XSr-9zgUJn>kB>Y)}J3!K0uw9 zqwq4zfNl9X%-`Epg+YwbO7FYFG}zpIofM1PI@gvXp`wX=GAd{2lQ%Q)!ns9$BU~z$ zN=tE`73lB!PneA5g3`qaErInKVt?SA_r1-7{b-Kd4*LOM6C7nWoSr_OM^MnM$H0Q@ zfPIXXr^W%%)q{FUz^3OLl7XwQp}?n&XQbB_{WLN_f-H_85n96z?Ww_sq% zwMx|u&Ce8Zi%@p6iFIA(UPaMqN1IyoXxM|^Rbq%oPv+fz4qTu`H9L8wS=95?QF7`k z+2=8m;7xe<5N2dkx9L~(EDVeJ7lr4)+rF>*K=rXoq=F#R&3!ssq=~LhPAO;4o*ocM zN6m+ziX2Qi7q$twanc#NFEuMR#NpYxP@oZj>AlGHtT9I7B|t2hL_k#U}g*hwb`Xk@Ow%j!w)*S)k$#i&TM z1X@vpP(!o{TuZzooW#PTH}|zmK#vSwjV&}Gp}*X7Uf8t!d`=v7KGJ?~0!!(D(X$yOjC(W6xUpC>(aGQ^=2?TGeZgX&Z^3gy!_>364s#gW)STc; zXTI}+yHUCCs8hTgqo66=%^jFV7yharrcE2_oW!r)?UlrJFWni}xKpJQL0=*7kL85B zp^QwB1EH5aQ1|p!ogz=?Ym?KfMHiebpNA=Us3j`75Z@B~TlpWuF0#@XdM3hQE$;fz zd*#mxFFKy5$#b|KP-iEQh$_7nx{;qz?2YA~vrw(VctxOZxPJ9#kh#iBN8XyOh*$r^ zFqTV}!MDhUCLxF=wnhp`nk_{h5Y#p*8U%j}+`v zfP~tlwBHl$68p>77bkeT_De}Eau-YmTrew;Un9&%9@ArDHVQxqsrLR+sG+J#jhvu^ z#y6NDOlirK2)kQ;|G_a&Zb$Qe8PbNVZm7>{D|XOABZZIF&TFGza%@LQ+hCJkPTDl@ zQ_u}EXPVAQNjPe3aqB*OHQz+nl@Weo_Y&dBV`z2WdHKfSfrb;f?&yT6;4rI}=tCVD z`ITIAa=G0vH7;(aBY$8OI!}=G@}PY#dc6%FIf@^oB2{CSa(K%-8u3w9m1_$Y_9{;+ zE;ZCC%6O?C~OS^PXKM zKvf#SJbpO2F*niBGL5ac8_^UiqoaN`D_>ckM5gjJwKv-{)X<{9sg-rzs3}IYuUE#u zf-_H~t-423Fx~K_)4Ns27b9-xGP~6EW}Br6dS|BNGoExyUh6vHln1Gh)Ax>gP>Ra)QZOC~jWx3PMj9BBc(UT4UeZK#vkt`nbk{pK4jfvGP8CmW5l9fzb!D|YVBGu6u1m$ z?Z?^8%H$l0zDQr4gXr=u4>jgI_6ai=vzn#mJ<`HXnhTbeGme;5AKH=n&{c14S~hxU zz&q`=)QZl>mHH!+Us43FWWGbHky(2n_C$h8iWt=$b zwb5wOjwSc24hms@y%D30QU$3u_N+uIxZNU^+X+Zui=R~V#6{o+YM=Ml${h+s#h@$w z1R9q(2p2zu?w1~TA9$}@FYHTa=3bvdEvm*p%#er$|MFFa%*6g=bxX75T1sNzLrhW>W__*8nxTqJ|jF@8ln!bmB3DYmoDHJy@YEheh zt>03Uy}aGeNqf~$+f7o;w<-LfZJ2(~v|X^hN$+8;mlojg*ceW>v^3a#d4@4J_7IlAC~JH=(X2d1Z@vQiM34a_jAGMyf)?!^RG0KVn4@D zj-m=v)HPa7WhE=rmJZ$V;Y*U^q$TRK!>Y?r-ECwyhF6V|s*C~)Gj)bnwt~A^z*AGQued1BL-3;Ut)n)3L zNKv&!56P~3>D46RFC>531d6?hzW_x#49@;`Fty!KM6!Y-o|E5U9hpk>>^1OoGd{@_t`G;1**ayrz+n+aV zdqoH{<=11X0u!W1)%-nP!L~hj`f?4eK3-OV*9_{8b~#yW(U5+7vtw}FxnJdx<5b-M zSDX)Sn$c6SOJ~Sbx>AhacdGgGgSuUl_Akx`pXMm6$C!LNQxu=nOI0Yic@c|$y9gMM z051wFm)xPp81@uZcr7})xlQ9LD}#d@U?KvH2Ooinc+kKF!;PU$3S>`!H6Je;5q=OdI%FxRc2CJS{ky{CD3&d#w-Jh<8A(b{lIG8MbS@d-^ggEJ??t?r(Omzdd$+a$h!hK zG~V$;yZ-S57^mFp@bqolw!zHqVGesP=q2c|MuuxuQU^4!^64%^IW*^zy&O2N&9?9lDRA?_PS{OQI6Jd@LtV8MR>pK_9Iu2R|gQq}P%ZiH^W^lNko)Snl z!KA}6i29(F9N7BcS`AKx1#_LP}ubCqd!SI?Q?f;Z)sUsl8kFmb0`6!Y<2AmZ~9hf zuOEQPwXiD9g$X{J(j=H}RZHH^)gEdYcK;Da$LNF6#1th5CRgh&tB(Ahhmi&NJ$ zO|>aNVm~*ru&^)zT1;U$^|-M#upFEE@wY#H@GpM~W0_`OYGUBzpqDXG);+X2T=s{6 z3fkRWIWr)#lj}aurk(8~{Y~d7Va*nEq>&L8dRpTZ{SxhyIR5=l^~k^cX?v_hLg`7H zb+;{cZA7&i;fj9VVk$wCO3CqfIhLaSQQY^ek(C|w+k4-B46B?kl6QEoU4r;fPWet& zm^UTj>QvR-Y!CC;C<9uIn0Ei=W*Lj286PIZg+X68ObUS3EtoC1|9iIVc9ma0WA(Rp z5yEYJesi%s`?C9&g_61ZL0WP>xAlmw2f3K7+Vqe%^*D5bx%;2LkEbX1!t^RnPnbFJ z#g2VD{PZ647U4>?PZ|HxT+jKE9m;A?C6pMYyorK0yI_Xz^G)jEuOs};^t8u52t_d( zme2ckH>2j>2B4l>08xYnp6^GPpHNh^1j9{lE~Xw9x%T|p>T+t4XVLW+34Od>r>t&L zBpc*}OT!!5fqx40{oMQi#cy}F_hA(V0wk#M*HTkU zb$xIaH1T6VH?y(h009S&pGZy&^eK%X^!K~sd$f*FhlC^q=XwVsX(Vw`bWUQ z|LKP@_|Gdq+s*3!E7vv`HEp9@bP(l1;T*-8#wrA6X+}ybItY;y9dEu5bv@OR zbE9~V-OoM$3hl1(gSZBX z+U;~BLU2AK;Bo-~04#?I2`-sZ;4nWQhr_!FSd>x9bMA=Z*(tqfk=2DFK{GXWXqK$$zpwmhZyVu$;tM-g83o_BT z93m_0pP?KLcuYz#ki$e#r$gteFm&~l>joI1O|SNL}r@Jrj-TC8%Ia9PCl3Hmsi<4j+282lxo>Ba09fhd z!p41hB8ZjK&1aH31ziY}Q$^~1-~E%;=~S8ykf$|dimX4Pr4@qvKearQvTOdpqlDkF zzgn0a*wAU4AYJ2!?|wbR>n(_6ar$cY#yBdjj!(pYrk5kvWv%LFqW!);`B4!wv0Obo z)v(eO091=j4LL)gdH&OArOVQj8M=gBnQ_lkP5dukh>PSho}ek@|Cd)|K|3cEe~WXd zI2<}cPyWP{=$o%}+Wy^_#W{S+zO<%q)k$cws_<7~L(I2H2fw38%-!#p_3Bdf7%hc0 zv+B}|Cye|#{0@t~wVyx8;p!TvevWi8H7)Um_j}Wx15Evxp&S5>5q^V{4kwX?*H~ii z6d&e&t>O?ZJ*Hb1&*LE<|$Q!SOdl4;$BSg z2@P+(v3xG61&cnWmo11AAbf9qLt?1Bu#}Bot@v78`_jjW7_4nt9(70@=qVy~()I3l z&2#9BU7%1Dw^|Bf`lJxKPPFdG6lVTE%zb54m0Q~`24W!{(t?6Cf`BxFVo(Z7Hxkm) z4T_YMf|Qg3(y%DW1t=X7(v2c1z34vo0(I+t-|rja{5j+BXOHbzYd!Os^PYEHcU+<= z+>ebFurl>xf8{Jb=)cn)**v4@Lmtd*Aj!Elco5Lw%k`}?Llll>%_~o7o=IvrlmOB(#+BBoC-Pdo+MTu zp(fSW4Y;7E^gd<|VkuGjUL0CIGU#Z=lfpo7d3iSXd&X!-L8VC$?-t`dK3fzh9peygHS~v*x9BRe%3hX5q;8Q0NPLj=itn*!p#R0vpgu2Tti6n*miuKmar ze>*9zzhzIccS;0zo9q{u19L&w%Iq!)Yxm^ZH0ih4P+xKCZ^LFbcprN*yvgULuM6V6 zgy{%Uv^c$WMEHB5DN0XTiH-R!z?&CqvxbZMJB*Xki+Vi$ToBPC4i>FH0MP?)evZ#Y z32u;Tx8oc-NS&D3)P47j+$C&7T5g_%kZ7;adG@l89b4VpaDgE@Jk>v|$$TvddUZJ>TtP z&lm84DX_QjL8{z2nKJyKWZ%$bVwOzxe?CKrfm2p3yqD4k~V@8U) z_5PzhC&keoPziTt>^A6+7d))c;PD>qu3wN2YX&+B^RbOpytOQ^l4}}LjBQ6&tWm_a zS5qJ6@yFCO{^(04m!TS10DsMaIYsl|7k(n4wj9h&P}J=8STTiE%kh-w2|CKYHRYKD z&h%3HtIOsE4UFee7o9>{%C?8GblyLFr8@!;JgZW^%AT?myJiSGTOK5$kCIiRJ7HIB&HtkoO0dmS2gNvaZH`ZuwS|`Su8C3QS;%*Uds=i`bWLKSE z;5@@K*X#5HALG~FI4GTxSU^xs3nj>7D+*dQbd%( zCU@a-V6KkCiJJKsw+X$jG==imk|>-QNPrZ}t$FaH5o9wG?VQb;kMRx6t-c9-wdJ#h zUFkFC0Ar8$z12otUvjLPt6|tX1 zbw&W*YvPqHWO^{`glbD-h~p^EzhvG!Tyukk-duJ`^m7`8`9cVJv5TxHC3kg|!tjF( zCD$2#b_e{FTWBeEz5MQ&{?XrV*To$33-<3DW)JHORVRtV8pen#IGs{4Y!UweY##! zd6=piLY_)>$S`AF%cC$6aNcmWT1qEBjo@|A*jX>elA9ICMl?)gB!g2hEyonN*P-* z0vJr6FsR}X6?QGnV$6Js&!N4U7e9r zx7HDp3)-`qW%k$tgJT+C_lQHpMSNX{~mK{RQTW7=c}y z?$=BYU(69Yc&X~l{;)M2CTL=MaM=#3Q8Kihm|}~wHT?iBv*VH;mV-ptXpCQ`)IRz3 z-T$IV&`wu5S9$AZ%@v~_l@ z=Hi-qAtUD`mOy9PuDW*|KosUDeZ`Jv$p5J|h}+=Ch7gJex-@uJF_TD#Og_nOE;soR z8Xk-1-3`%4L4l6JHfo}pN~U6Qt1*-bCI19}JC|$I>X!Ie^E>8=FoMvY`LT{Zh1!Od!2zV4G;%CA)8t+-ldaS<{u^O0W3!8}cRpTWZ&)vJ*0 zL@<~0;%{5-?W{&Y$yQ_Dm1O^!)}~&kmuILR?Q&g2q$+$sJ&ky188C_vas9;H7c9IS z-GC!h;#Nkg8qTOYH>qby;4lw4I+gMmmmi;$D6e@01v4k~-KQ@kzZ=bmeCQk&18Igq z*PMH|{)@oh(HSApa2^MlZ)&zvDcIWN9RRl&V4QiGYxJ$PtWSIGzzSAgRR~mfgdOKG zUt$|cyqvQz^`rxW3v@EiEg({^T+oFAd3*H-xA6WC0Z9Hj?8m`oD+ylN2s^|x^Ei?H)*G~D)AXARujoZ%h4>@dNw zv^Y$YqqH-6vY7LYpkpBE`P_TRq(@_IW|r8U;r|5-2=29O-6Jec!i85Przla4s5R=< z{621~C$f!FMN8%g{Fmikbc>IxTcGA1g8PhgA}cqHd6zWT)|_VZ-4pR5)eZH z0KuEJMj>23-4Qg>y#9{ga(4UU3YE`zQ+TN}j~oS22YNjE%Y^~knH>UZRtl$tuqF|o zZ&&nub`;)SKo<-Ex)u@^7WSqF(mgG4B5p7|_oseXh7Ch<$+vfmG2b~%&muH3Pa`&0 z>)6N0Mg`3E5Fr&?JK}pK6x(}y%ZjLpc<>M#V@5Wmz4c|unR+ZB`^d6m5z@OJ=l5k) z+NqB%WSgh{1<6kN+K=U()%B5PSAOdd#aOz+sFy_4ru`|`%baK49i*o6!yIUT=G!{o zYz4UV9FJnT0_9_j=FHnL!(&nUGx;_rPR#8%{*G-3)l)Reyg2O6LHU@m)drQri9anq z`F8B>!QQgvR#ct`il(lY*Riz0V+d)mdns^lsj0_#suUqYKd{&PE++f2_SaTf20UN; zS7cWU@=yIBfZDBj=fndSe(W5wdLyRkc|*onojN3(hlq%2x{rURX6V6%ma&!@5U0Gj zc%+QZ2;ko#Gp2e_f^=s7T@m+KgZZfJ2z8kp#gD#mMTZkQKYSO*p4+?toQ;`Xqbyqz zDSu;sxB%sK!!kV{Y6L)VUi~}Rkl)TRuZzoD$iGxuo`WTOsza&I`UkB!^T1TWNLuMT1gJ zg3bdTm~pVS46QV8veQX*MwI2fUvPsyqaWe(-k4L&tdZW}Wx74v)Hp_ci$!PFiRrP` zVp<=#pXfjzwaf>_g*I;+tiFi>pp{2dO{uzDq1Qnh0_ZisODR-Ej3ZFiKrgWI~SaKlvRN_#Ww+G@10e@}LZRr%(Nieyv| zSU9UmONzcW(;KoHA4$LWTY-f;J->0^WkcTO_N?SQ{SoZnh!i#B+HT^i0P1Xf4gU6? zp5y+C*vE?d*L6nlJWA_D0yk1}7GAkrhoX-_TgqXhwHNk18x(lDnoC$$f(g1CLP&1l zQfZ1{`aQ=VW}CMNZ!9Rg`7RS4B{2|iP)VWtcO<6YP*?6P zlNb(8K#)uXzv^&w>4l3g6jkozEUb$}WYE;fpk5l_66xwNbJdjRmF;IK?w_h5(R8&@ z+`)5DH5*;Z2Y91lG8HP%A#Zuf^jTN{>vbpAv3+$VNT-Xjz%%G}f zI7x`zsjQ(QsIEAS>|v@7=H4h=Rg?nOdd38%KS<*Eo1-s!dvriS9$+rXCv->3h4Re% z6|N=8THjBi%~^naDJqB#G(lMm-auBfQ)M+@8)z`|a+9IFCd8vWGw53vgA0jVmNV__pLC^lLA=BG zkLQBF&4v>}D-*mP(!ri&Lf{AX7tAU#^ZJ}dPQ9Jr{&y(%yI_u@nR%{>tjR8ub}II| z<`OU2Ox8sh=ah88jWP#OgRW^!OiuRp^74Xtt_C^4t?Ge=^9vOq^LHuJFL}Yu)?$kq zvP7*x##fICTpxP&{c9jzn(($n`n%y!y_xGVH1$pBD2bH6uafilj+-6oLJ$s-u4aMe zD6U&;5K~_rV0*!C7aW?*J$wLn9HXcig<{_cazsTvGuCR}{zOyvPSCPJ(CZnZ zJ_m00yaF~C3t8bHFEDGJy3zQlp)3^nG0h?5=K_bsoS;c1kSQEUvB=icnUQVe+l1BWWW#^S2g5 z+~3dOP?e?-ngsYzUoq0zMD=Mdj7MC8;;FUw4PD2Z-&B<8sb&aP1x8%txu~#*y2bF9 z?#QVwhZ=WchNalma0s4}Mm!GoB_S@iiv0zgRMzOer$5Y8D=;}fzOR_7HVtToWtk4T zlt$KJGP4I&RJD+QiVYc@(`$E>TI}aW0V6JYqkz2mpN-PAjleZlV2x$#A-o3;4wUSj zkymN9p}x>#(YGMaLaaNItwsIBhjL=9*>_aLC|4J^A_?%WK3U!;l#Y@d$Sc7W!aU0mYg1G!KOmGF8=J+*ha@nMW_oxd5=i ziEx7h+L(!RjouBYh4Q-kb8k=u@v{|Y_HC|=?@Qg&{4Y_!<1naPpFiWcpFzP00C=W2 z?_M3$SBW#UyX|&p^j=mp>P&guk$PtTJMYN;?Ifni-tcJv86sHR8pBw5dwB2dF#>i? zZ`fBasbolcXIAxO@MzDT)fE+FOxDA0PZ>?uwWkpA5e#kF&aYHqEtDyLxT}}rE)Vhr z_(O)AtNDwVy`5MyT|J_l8rdvDAy_)uZx4qox8aXOL8%Wk2gY#9uCAIsrZl4cFBVtOMWovYYqL_4JI` zTnu@wG}|;gEm+^*e{rEc0JNynHhZ{%JK67S9_X!wW7BKp4qB$_TYz^4lcYb$x>&(m zqpp1UH_j=xnPgBm^OE8Iql%Em(A+p3o%K4q{~<1N-RrV*_Ptl` z>0_pftJOg+R)U3*+Q%f4v4AmsjeaTqIe^D-1|cBS`xP1u~shDhbJ z&y={j<4Wdzt{&%an#^ke$_7#RIuzI8}E@k8c6MD(PdT`m1KR+g1b z)L@TZbx{MA@DXN$;Fd-`Oy0e=$mVZOVF+v}zD`dp8zj+m^knz#Z4EADb?6fqBqc7> z`toB*UO#8#BQ*9TwXEn@ynIP{%JNEQ(XW`bmb%=WIa*7Yb9Qib@XLN|807NRK-|VI zMs|Sr1kp(3ARP;i6GG$Hvz=6Cyb@xpCYMCKYJiwOe%@T_NAK zlng1~acpW3Ybb4g@DyqdfYNnRf%qp21rWCU0>JSDT_OX>zofIJJ_+EFr02&_x2`zesORf$0-kC|_wlaJb`p|=cdVkuxwYNNbpFhrJOSl-iFKy7s zFOr$>dO_pqSE_Ek@o?r0{?)^Dc>{L3wVcV4|%SWS(HRSo}QkEjyi zPq*&5gFTIY7i4}R%H}sxvV+25NMz|CUXs}|m4X4W&1yOgNbGAwEj|(Z9k%-K9+p@6 zw1*&!0mj&9ZH1J*|EX${WGj;KMQN^zyaNE4$rJ1lL5nXXFM=-FCbBi7l&ew z|K<5k9~btx0Ox7XWKZQ~+PcPap?yLI3f|m8`%jkT+2XM~SrK*TOzcY?2vlMOpg^u^ zyZ+=s71tm20W>TU9^!UADfjc_#v5NS_josP$7{N6dlYf>6WfSpJzaPM6? zqYS|$70m@*GPV$J1E5HU2YULfCQ*-{kM_f#5=v2&{hYKE> z752E7g#7K7;}R!CRq9lKXko7yXy8`c|7g>#m%HD!`=KX_lF#KPo5!E4hQyph6;NIG zTUuaf=Rmo_L7*&yzg>Hi?Gom>0?G};@}PZmP&`=Zgc()vB!PiRfu)tJkwRDVCL%Ix z)T8r8MyBLGz)Rli6FVOZ0U;dK?Y8uDiA)YDJUgYU3gEQ%*1ncGI-6=7_W2%@E-i70 zzrEIo4C;B0TmmH2Sqtl)=TeXHC(f*P4xzkq$rYV)c|Q)Hh`Hnk{=ne*l6%9ae<;Gm zziu1}x#Yt#btQlL)cxJq<(gMW+Wsv|!2a@+W?W71pvA{wfUc?i9Z?#ja=t}LsWKF}nD3B<3&@FXa zNr-$qD{pd_vdE}orLP>av2O}F9FF6Dht&AXxEq?(gRWMVF6$?UcN2y_{?|R99%?7y9RKKD2BN{0P=F-*|aAnYuz1 z@}N1Bc273$XL;YlMr$l+34kh0BO791jZg-XPG@JqpCzIfShWAG5y;lar&LO;Th>eP z86T!~%2xDOUy2PgaL-oyiSgj3jiI$rU2D8+8GlhHNmxYwskDzk1%pn(K>hI{^elE# z`_w`G{a;<1@9vt7#PlZbw=s~9?R*@Bx)rDOG8|&W$uQpM*`z!80{CH$U`De^oI4ohwO6gZ{%)BOgR8#%rhZoF)9<)bH8ZEUrCI z%(J}o+z1*D&>8Ow4;omF%Wh9>Lq$9~VHkR_`%KwD@`6rZ0Jq)$_Kbsei-Rf*+_cG@ zW8~e4h!4GXmEATP0HTjnyrJn4O-V!V2(TB!jywwr!ufl%|H`b#nsjK|4Y5!VJ-Io+K;#2FeU_F#28;7_iJ{mGxTpUvX#IIBn9O?+g96EGqdvKkLwO z!?OPai$Cmz`eXYZY5@m4IvfQq^uG`1?~dBTryPjtfq0}G1@Zv+-USYhm;ZOq1A1!o z^BRI-8r4CW@_()B{LklE&H_)3+}}I$_f`A%kw194xM)E_5`N^^iIvuPAb*GU{=c93 z|MGkfP{jYg1u!Kg1z0tT46ayBNa5fFMMRMPz1{z-074H!FbH6Va-P@!oj}yQAVLeo zT>po^59hZ40woqk$E!-s)E8sqNn4(Hsacm#v*lcMxt zx%lB{?@9QRe(IS!4UG?pH{qYoO6%(S*T7DTe%(ke z14E#k!w@IH#eGY06j-ySNijtKy^gbFSAAd|kZ+ScSVuVY`9RYJvo{8m*rWRlPGh(; zI(OYTtTZ@$g5F2>BhEuv7E7ZyRgSywK@$g6XbsT53=IwffCgOcRPfI?bAkJA=`sh1 zEf7R;c)Dr)_5LfsT@25KUKI41cpq2whp~%5x6Bd*RB5iMh-dJ2^J~XMhZkFc7ZyFv z;pf{yr|Ys!shGY#J?l}?{f+JIkcuDBOaeSlP2zio9MBhKNQoip?xytj~hatH7C1wprn6A%ZLT*44_I&iQLU+5GLweXmq zEv9Yo;gO#M1J}N9Ro&4MVs;9nP2(UDaPU#BN}wRyj8}gLo*`^7hIJsJbgj=1xNw9&ihjKC6K8JWXPF8EOtKcRxQ#v}b$t<5c4N_kDeRuUg{R zO+wJ4I*Ku}wf_A}(BQNYul@;!ml7Y*Ith$@;(G-Pz?BaFXFbsxCo6msv%0CtuLaW` zvr2^Q&wCs3GAyhX76Fe1GECB=KzxcS6k=j}0beydZI_BTI>5or9ollz{e9)XzG(pz zx8;V4q}MEHV4b+o@%eFJ0O(q%PN^-P!SHrt`+LJdE?~ns9@XQ*#k&w8eGoYG<-?)B9 zTAFqAefepVU?48$7r+R^`uDE@T^;<2_90C$hI`8r3Pn3q-iE#?d$NB?ZbL%@`b7^Z zdND*>)qsai>8A|r#H}4v#U3mTg8*IQ0zV2pIR}y(8!2GN_IULskUo0voyA}|yZ7Yn zVd2`r=RZCNQpm-drIm?yLJp*G2+CSEb&#EkXxWE$j1`6>T6Y$Mj{T{N=z+dGoV}k2 z`uKoC76W4{3{AHvtjPi^*ZQ{Ddi1Xk17YeCJOH@t4<6tG4{(4E#`Z}+OgK~)iaq7O z7X+3bL;yNN-=ZZLY(wwBfB=v@@bd$UZeK&gV`!~|SJQn0vtyy_j^Jor-ZgrQm8tud zG+IEncukq+&eway&<_fH%t<0R7=BOx)`=V!Konv1KwStb*h{PNHRG81HZ&i?@`z7u z8urrE9-$G*U(>!md**OYIWBN=2E#XyNF;p5OAGo}$IJz?pgiKK^Hmev?-(zsRkpK1 zSPwrQfsT~m6(U63gnvpg4R8IesuNevVQjwpfO7^y{)0dFQ^4ek))+H0l71K*$3y6) zY25~JD6Ro9X!h;@{tXy)Fb+}+r6OQ7&VB+~+=h`gbTSZ32tEqxfc=k&x*E<^J%gm? z!Jk$oFxsNU40=&v+r)t<_5+U}njlf5%n_!h0mC%>v)^-ylmF#Sa49H0!%cqr^yz^x zy}N*Ql)U;H#+Y}vFw1Y#M2}|Ph7*P|mxB`)7e9BL$L9$~Jx->J5)Az~T!)lWnIpGm zJ~P@iJJ8lw$3(R}NU_{^+WNgQ!MGg!eS8kRm7o zcW(}7*o$=FLZ2b{OmB8fMI2YqmXr1ZHTHkm2C&J?pwDmLK6&sBGVT}f4QMm|U%s)m zg|+|?(W1Wr0`!-h`0?rf6qLg?ao}G)%l(f>aPW_B?cZvrpa*>XT=&6$YK~!xH$OlB zTg<|M4watI3ECBL(9Z!?VBSOv4g(7d%hjtZ(1iL+0Ir5SI)ZWf90LL^cOv!@^)c{_DHI z{fFOXR?GrI*0a~;&?=%|ja?!sWP_#9IAO@RW?GW;()55&NhX+emxV;Ju5ucKAQ-!Ib(9*R8p5DR}k`b z7$ktre}d)gKXN$bD=f2m1mh#>?X9B}XI4HAmD+7q3{e4z>bYZQejhhY6#QOP?8HDe z!EM;1``2JDfgCYdS7`CpcdPy|hjIC(E~tYM=*ABqpRG5BKXdc_R+0U-Z-Uy=Qky|F z1!Z;%RFo%(PyD(iqCDWiatk#VKC!Ssn=DWCzE&(MW~sfq32w(&g0@F*Vd6FW#kZ(4 z3!s50n}V97c$o*F?=Lv;NYfu{1Kq9{H)`;0Yz*!J$7yKgrg8g*P%r)G0L|~<5fUcu zR6A++RG~7<>^JTz7$4Ha?7))8VUK26THz2s87PJ>--djIG z>aw+R-}M_156WqLTcqCJvR@(7Ao1 zUOjRbgOmvD;}yr*bhT}Bh!D2d=GxlYE{J<&@3*d(0Y@nW#CONx{raf$+}L?|ps!!! zgHhwT&=`{G{2H+)ztQHkO;tPD8;-aF( zH?UF$dU~e)3J{Ia&->v-8)p6asj#C+4YXec;3GrS6ruavvkS)Hh;1gMryGljJtMtj_oH(fq87Nw zihI`{w_jX}fzf%!;Orxn^xNKEAH}SM_E<~N&)OpJB4w2;qNRT<@XedMGK;|440c-L z8?-S)%Pd3gwXmCkn&l@x9=%X^lw7>7{L7awj|`kvrmjH$G}OtRr528^NKrE}4zcOk zhS1+;raJ)cK@h;>VY0B~D)g{|cR*dTv9STfs_g0|*3~|EJFCFy&b&JNRp7!Is)=0b z*NXJgZxYo=DDd&)&%!*nH0VWd++S#a-}LsnM{lbRrr>~{c~nIR=uL&U$QGD6|A-)~ zSQc3bKJ-7gj+a!Iu9NYA~f?2Un%W> z{d5e1Ouc%DjR)23z$b6KXJL`O!CwVYP3z;3)u3FM3Nac(3rsEPXKU)}mPs}Tbb-QU zu*@kOM+h90IX}+o1vE*tG?fsHT3ObqO_fjXC0FpW_=I5lZDZ@<#JC#IQyQS#)6wzj zm!G@8v!zh$g-u>R1sU1R6oA^#gI+{%R##A1Ees2}h8$mFQaQ(b&+O7?A3wkE2((Ky znRd}q@9cTb8|QNyO6symzjC5u%3EAqT%jaE6nvGMAac6Peo;G`-MTaOPc6MQ*Q`QC zmmDN#>Ag|TNq+?ky5E*)uU+l zpwSSdt7gWQXak#;UMk`(5}$%11KEzy6hRI0v^>K+^ZHEyIWLN&HC8AcX=q?@i&k;X zYEy_9D9SI7=srzuZE0!Qxyj4RJ6?0$<3vLwLQGWDJt!y$fmndyG5f7og>CKZz!oJb zIYZEppbYV^FXEW#Zy)((GddL8jID&b^7#IhSCNqoEnyzdp7oxZ1P8lsT2D($%g}J@ z=`pO0LSyPsF#qO&a5sOSo^ELQm@FMtORt%y-;8F_emp-2T{eyTmsbP;xKS4m#N??B zaOr#Mv7j6HSRcley}plX9}Gz9tuyd9j}Bo{7|wb%db8@C&zDY zI`Qn#s!4z-o!84y`Ed8V9+v!gD46#0JC7bEK6cRX=@GV@>$d^S5TdmFlT%Pma_9rP zn^dzuXDTW#{t{1X1@SbRzS)H0yn(?aAWgyM*%O~&FVURP_KRBluS8)C$!UA`%8I=$ zKl2)y%4rT@e8&*m-oa7PCWI7C((G68+@^c?cSm48!lt?Vt{Xqg^F+Y_fJm7~E^5Ni zwYp@~^0K_fYx7H=5H$LAbrf-e4OU}s-_neSLSVv#0A^&mI%!KfE-rcJ)6vr1*whe_ z)GD0JCp0AgXVJjAUd<&`d)T9-l>6w^ev;Af3aD2ahDb0Lw5Ds2_LDmCnhIv)jjvlTb&yS z57ZM{3>cviE0$A2-VwX5Rd+);3Mk!CI^c!7-Pc~5?Q47)eUf=G3Ugn?$&QL((smd} z@J_;mJ8xE!c^=Z*yy}1OqMymPKmEbOjx@i{G$Z-t{IMBjZGuYS<8-?8&#rqGVI)%=zF2;3x$jsdH z5Eiwx%;gmB`(WHtN9~5RThP!DiY_iGLA77?A|@dj=Wf)qrUuJzW-Yb^V26{VV`t9r z%O6+a#UL+gvR0cZBX+<}juUu8w3*z)FF$mvl5 zK0aUKy%|Oq*Y`-wtn2yN*&huOVb4QdrX1|;eX?NW^LxLu*x0@r0TNY@`@oy=t%Z0i zprx2>f`h7V6#TH2Q_ojVODJUrZQ7zEgoF4-j^K0dK`{l2229pDVYClYEr zKOMg?cNlarr=_QFmdkLIWNbPqDJi|IVEX}^y>dd;e(~#5>yrw_#l?5sWs(z|O!%Oy zw|43VtG2VVnnr?GTUiNNA_+cz<&^~?@zb9@wfpTdE3hzTuQsYyHHvkV!%9ZK@Ljt` z+HL>J_}ab1cF8RDQhG;_h#QhFE@g)Af{M8eoy!+}wPIK^=77b8(I4s*pJL@t@a$!CKYQ zlxNPKec&-Hsi*f|kYz|C4G+I$UD$4(0t*JNF&_aS_4SFo+`>Xcdb+$0q4l~t|I6Hyx*?m`&Ofwol&v5eE=_TIVWC}5mbxWE`}~ar@NLJ9D!h2J5SkTX%kJvx z>US}eNcYsaxQ}Wj%Z-o|Z_R&w-ws$?SNZt(&W`gJr=5)_aA6!QEra8teSJ@vO_tz- zRcb7GyOJ~d^(&2|kf7jL1=kn08{1%#-m?mSpPXbU0Y^48OK+F@u7i|=owYsWrQ?U1 znv5MC-;{#Ra){-G&;)O8#eqrX=&@s=VgxIEGh<^H9bq)oI2(uZ?L%zk;|mx(we0d1 zz?zH`Wh8s2(?WClO1(A&G8MMOye$>)-NUteeD#gp-4=$&r!-bLYq-b8O*dA zMlNi#v$2JRhN{CLf(}`c_X8#+o5^u7HTB2L%*_km0Vl>Xzn6$Kh1`oF`{m0o2Z*dy zTj)q*5|cWn4ebD#7I8q0X_Ucqn-Q40JilL8SJySgDG{G-hKa$c5|p-+<|!87pqI*r!gxXcb?7e`_0?aa;ant0OM1NO06WY31eRABVWRW9%iQCM10C z?0k(R`D0V#<7vys2`VcqJKwHXS|w$y5WMxLr_~Dy39(K;j|hMH^5YyG9i636w_WD- zE7z{o542xvG@&o47>5B^t&QsBDLFZ>%#s~$y#R&!MF<(~9_Zuq)f9r>Vwc@nKDX6g z5DyMFp(|J35ekIO_2tz~=`1C_ed}H|^|b9Bm9Vo?|4QDb@s5?FV@b&_x%F+7mvN}d zu!*j&?#k=HDeUP~JPZM%3ZY0c?Y*(-5{do6nl6*+s{3=Q8ZuH+gk^w!DwM!Y8DkH> z47|_r@nU^9WQtF(Tqr_r<+Hc;>LGoizi}q|`T2oPMiyV_zmYX5O5MD2KJmmI3JMB1 zp-Xc|q{x0<9x<7g)0Qnu;%x!(f+pY#Z4=!OHNXxjWLfxVn2Z$0nVz2yb2@hPXweLK z&XzqApaj$yNNCVW%DhuIV!N2bZv!%)b#;*mhMH06M^u=Zn%Y=deRuyr8Z#t;!1`jW ztIKuu>O}?z%wl&GKYJ|95`5ANDZSS_0vIL&uHKy7l{?%#JTJVHqk{5gcq?mLRl(if7bUEAwzUjScIC*}>}*CgBmz4&W-@9{)1L$zg8ox_6%vv# zIDNh>z!~SvnKL`w55IxCCP5;hN$rLm)<&7zze_xJC+V)~kulO=usb1Oq6&}Z3wpZ`Q5a`Q{%#%pqdop?h><6T1gyI@A z%jH5XmD7ap$kI_FDOkoPCQz-Dt#OJxBxsY4wLRMhnh2wSyh1#Z?1=gG^e$U#>$d5V ztgNiU!tf@>3_UY5Gl(=QDn_Q_6R4k^FflPntTHq+%elc=#@_C}ElYl%;O}86;0I|%=%i9s?FEo!s@`Y&X z!+2pMUW^C~*APy4aVhd&a!D&Bq+Z+_j}P$jr<+0356=ExRK}AKbrx z!JLzqH(3ISu8yL*z{M3ZFurw4Mnff2014fQ z=D3)caRhmX-=`_Xm0c-=0lBB%{4W8_V!8v_PWl_(R7&#$wQ^3 zrN!t8g6$Ga8t;c7Pe=YZSuMG}xw(0Yi>1DAKP`>P3<9GGIF`}gDUz_=-Ce$PHqFnUA0HRi)L1c*mxdIwo?e6e6JPKw zDF_G&tJN12U1&ibZ}mb%Ba=)p2P1a}Wy&o=m+b)es-zn!b5fks2sqc!-K}uEw4|g_ zy)KZFU=C)&PvVGGgrV?S(*^?7wFWKxpEoQ5>pm#>qPb#vhBBguxr zt$)w@06>=ScttP7TwR5E%R>b0=6&$U$jFMW(9mevt1Dnb?H?svvs)M)rB#`FYSq70Hp6Mo0{hkd?%&6K zns1viBhR{b;3dhCkcx$ha)=*L3YeIfgTt`2IduCAKDGq**|V2FkU2X$+gp*4kQCQ{ z1jn>klJVB|x3N#*VPT!~T~KlN@so{>%^+KNvXViXiJsmiZxG$k0l?&|=HQCA6PruQ zXxLd>rwpYxRM_R9$VsN!46n`*(huK7YgrFMnBUq(;~sO@!A=R3kCqvTCEAd=6QOz* zF=J;pU9&VRTExqCDP$d_^P;0E%;I8WCu~9PXif9)f-VFN?Co8*xR`C2nw1q+7)sp( z?@w}x29hI_S=irZW}2XOiMt5QjRAiL&We4pq)cLs(nB)j0!KZu=vvfI!$A@^!l64X}q?tmM;ED zN%ZrLjg6ygtMZ$ko}R+y;4u2uLYz6gW7@tBGe0RbRa6>%7XWYQSWpZY7Uqu$)Ht=m zq~a_dfYF<}!TP!LBnF4+ud9FB;cn_ZH8pU_{gP$l8WS?*Uxzi^kuwJ4_vLei{DRYYy+$#_>JZ+eaEXcbF*ATfmu!BEc zSi5A|_hM}95jP=*6n=#%xD6Od*uT7+Pnb%5{GNovwS$8clgybbj6E>hmU{|2MkYKW z;_}=e69-2d#{^)!mQofLi{K*RcXxJPOm@`s^6T{U^0Gt(n+)N?z}fb_0L6*smX0Y~NQ;>H z_#)ABZ{G)WS5NCoAdd49U}GdnIqa+aON4XP#gvsJ$yNf%$jL#N#;-tpp&%#6$-$8g ze;MqV10y4#BpAb2KD75(+)LUz4UcbhCyWdWQ&3c7&`&n{(4UpXCL$ujC&9r#@IWS_ zq`0^SNm)F#NOJn*$(MnZKfsLzpE5oTV;7f@@FIx;u8NAv7#@@W6wSc6N{|n5{z28j zU4%KmmZmxvAR#dvJy>cddv0-UEy?sIm935rEibPkP&AzfXLsBb!0c*uH8uwqa8!}L ziyKS2+$S-lLQ#7k{ucVzvlmk{G9dbn3AT9MH!wi1FPOQ)?a-rK#6m|`Ra+}DB!LDz zr`#(cZ}a*cObGAx)vSYAtfB9^zx4GzE+T=fRh7h3su(!~9sqE#Rl>FJPxSREJ4}2K z#nOu4OgiR-8uLZ6h6lZ+9$T{k$X}8QMqY#%KMcdbAh=lJhRI!-zq+2;S~_3dSYhGeb^E(39kQX#0B^^{#3=G`fE6J* zf4*UVZg%#8gi?3w4_HbV52p^;*t>c`;8)D6C-dZpxn4^wE>;GgfPnsR#ekptRh`4e z9+%)ZC+C@`0MBw~AUZZyq4d+}Z;b+4cO&**N@nImk6{=ofPmXE{@#a)&+YCjK4`RoBP-OE-$GrT5=ZXdJsQ)cb7zw~T*2gC09GDINH%J$y6eNJga$HkOAT^g^l6os>7j`9H!$M2ztsx-$>+S8qC1q4XP>RIXsWOGr#jOroQsA0&*l%+8uXVGab#Ah_7KtMm;F6dykHm8)pla!yw( zvIUGj9jjMPC9?or&ytdo`1oh8U)!-zxsSo*s+4`1ylf9|n33U6rKPQXK_~zsRim~6 zJe#M>tFcfDbM0D@Lej?WVjQlCscD79{93_FgDYnsPWk0HeP*3QqZ{Xx+XSHgDIB3%GxxwhqmNtz-I|$^@fogtYbzo%GbS#MWDfMsXapdW*CHGZ zR>q$3fW^N1y|PkFT>Mx-UuWlyG73g(YilTx0)#X)JRAqSjDhctwmJo0t&dWWIl|7c<=eB>*svKVAn%=oF|qqvuosG`62wgP+Ad1)mE{ zgQK+`pssUsGdo`bV=s+6BuoM&ykfDV^_cOTr0WQ}`MTI3752+9;LOQ4 zx3T%~4L=;t3Mx|IZPiqfiHXm7`5?7!n30w?S_`4Z7bOLS?)LW4>FLMD#>^@iE5{^m z-0=BKzsf}YiY#CPM#F$(D@ZmoGn1g(Tjo@VPfbdCn?Z`((LuSBCb!+KD(T(3ZyQVB z8`Gk!7vT*X6+o3iF8@C5V|K_MdVPYuqx}l$w7cO7>C!Of{K=D7bvfozEy6j6Nkt?G9;c#c{)W6=N0Tr}D7 zC>nnLH*w32jdwo`(7cWk1_{<-ju@X-|5+1}Xzw zh9661c*MlD+*VuL+cos?en?+I$#9WXU|}$i2qc7BV`e5M@Gv^h386z^>6Fw|s8zW4 zefX`e{Ew#PoG%riiNIg@!Q%)y?AHd=g#`uua2Kwdw;cf==#GGLx$ADpREf<@*q{u9 zQC039CsfT;ov-e^>?kiM*X@{XZ4|0#M0wza$$JcELr5Y;P=)pTA~{2h-55hk?Ak{L zY2a^@&L$>Ob3CSikF-lpa6uxziW%!;2BWzVbI0v%qYo;vamMdkiP{^u;1iHPG}y?6 zBIQLdv`Bt|R4TcFfk7w`3}t2lWDCg;1cJRa{zFab`}Ol~rEvqsNbZ2@r(1 z=g&t%RXaI3^Zwi#uxC!Fzofer=(7feF=i&l#sj5x?B-(PXqTLd2D|=J|0yAj#~0~l zv|-ycfy{tPA3#jxJ0$yF!~o3rzkT~Q2(6qPz7Ww)ejAO10dbYn?^F)R_~OtTj%(La zPJE(=PoC^51^dx&fSOX!l;Y1TD1cn4mWjzMKhcFFA6YKPVJalOS_AteOdXV*Jh`%x zKe!F91YZDiYn2OR91K^MmcG?`d7U^%4|ai^jBIomfT!Yhs8dJvXr6`u-OpelFd*QG zHWt5NtTBtA1M0Z}3IO0I0@P9H@Q81lPf+l4$KzrZM`Po3!*@dsby7^s%$s+<$}Scc z^R?(KkBq^%s_C8H){KzM>Y z#1{CUsvtk}O9N_eU`kaXY>$%Ov|IIRqCVmtDJOg;LyBfjlHt6JJDAchQ}NoCSwNLh zI!FaMcQQV_I}qJn>#eK1b$$`zr9l|Qe&JG$RI&iu=}*3$kTd;a3Gjb{+wSO(N#I!+ ztgn**dzrY{`UKAli4^=ICgMY!K-3UKEyCsq2;EIddHE2-cMLDBN+B~9e?I}r6buk1 z`?X+!7#_hLDi(qS%Ku^OJHWZz-|)XGQIaHtq*NL*DkHnH%4#UGwQZ4P(;^|2L}my@ z_Fi8qnU$51%Elg#Y{e4C=3~ zQ5zp-XNyXK*a)h$frqyQgJ`gTaKiI?hV6$8q>!sP>mO!1%qE}va(9V)<5oTX#*6<3 zExK#nSX$OWlJ!{?8$4rl@R576e7Z#&UX_ zkant<0O(mFWA>Pd_t+(4W34n-)Q-@nI4?UT`8t^3_Hv8aT!FnzDGg$bym6o5$Aj%B z?d;}6L+imVa302bry}*^&g3PR@~ts$;=D&9Zs}ttP&c>YZKskxA{Vd*iBF3TWCmm- ze94b)Ld0?|WR=jizii;gn-aqa_F|7ia@O~vVpZ&r%6xiSn)|1V&h305Lj%LZ-@HzT zN-}x@b~Nql(emZYCCX!DMm}{B(LL^H!U2v%Ph;h+Dxx6)0gpXBQ&Up3Skyzk;LKi> zOP1JPjIVc|?7vWz>?EQidVs#@$a}b2SsIC!+N4lKmanM!-;0S$Nu4h4?(N;TYHRRa zFD4Hcx(9zR0;EV6%!(3EGGB~Agw<8+gv{JuD(7eLl$Wvk*P|mMq;1nU#H^KQS2etQamS6= zK5T2@rKVh4)gk-)G{-N-=|1o(uPgt2%Zuv~OMR+uz3!EXSNrUM$9q>>*bbGg)q`&6 zl*zSBqo6hnj#<^o*uMeXMpg7I#OgErqGDpQ%*^alu5kJH@#@5!=ZAXFDowK^S@p1)j|HN?m@~`L5Vado@jr**MjUjPM z__0c#7kKTW8`zk};Z(l*aGlt#tJZDUu;mbFYVEK`s;R7At{$rftVDSQ^#xkX1h?7H z3j6@$Cd0=p`kErE;l!<5<4EJy*4NAT{`LpHz05G{at&+5SisTiYj>Jcvcx&=luG(#+lz0b za+seo71Y$5YRC3)g4xs@a;3Zf=kyh*d>E)Od5>3#v0uecMZhmX!AYOAbv+iV-i7zD;JqrusJr1P==;sE(^0KkfimUZaB0R@4v zLM@F87kqmKMI)@^PEaA7u{^X#Gz0K_@18w-#KoIFe7O6wE2{uRIinXw$u#YYpwpn= z;dlAzkrJ5Ki4Dje?%BInmH&pNtzmv?KBiLS9IyRcO7t1p}2iA!vr#8pXARviuZlROPF z_(eP<&>p%Gs4=o};x#SrazS#k%VN#r)Qk*f3d_7Us8%OEChcu(knr$mL)muKR-_ho zQ4=h1Q07^yRE0&V_)j@?CvGC6%1A-5<3)gRwtYg<(C%oP(AW2FiiZzHUqqH?RRC6fzu+?)31LzF$ceiJ9#t$%JAmjOd3p*gh{gXAqKZpR zqqTlGH!uuq+pP%)Xdl>A{3M+gGJ`T0do{XBv#d-M5;j-X)a<$gH&uCefBw-E`HyfP zF*2_FL*m)LcSWPO=;G2+w|$L)0Jx<7UJ%}a-jd};0J5>MUfmqZ6zmtTUF!m@Xl!hR zUHm@m#V;&Ohzwu@K}+fBVffY!0mlWQsOV^*f5W$kzP{QkAijq`>Jtgm+qO8@+x9ux zJn_`u-?ZqobQ<9~dh{auiZ)AW_21?ER!~4nm{@c&xaF{i0dG=p^-tKR_Zp$Dx;JyN zjUvXYZDuPoK0|VGr{1_xO*L!eV{dg2!PDz=t~}p`@8;B?#p0;s3Bss$2Oc!O;+b72$3{mhdj$mr z-#LyL7#fyB>$KZWm+bJ)I>BS3w8_P@XTzo_T-J?FPwSoPB749nyuCu!3opl`^vckk@f(|FTt$!7+OEcGT*b3G7g?TIyOoMwy|IZv~1w_}o z*xLR6^>A!V(s?PDbqy9JWMxqj1t5$0)l!S>{*e70C}=OImXw^tW7zDsk`hmW?)t1X z$pcBnjqBIt*eMIdS;$Lyxw+r}vm6hRY4@)h|NWbOIv$uV{F;Tu*>$i8QTENymci@8 zg96SX7TqIv6pZ!OxVLW&H1zs2P@aoHhVS3M&&W`Zu6f6ni%bZ&kP>Fi%}>b5%Ny2P zk$}A>QX2DE*>&yh#fAg`ke5si4K)T_DB5%|f=MA)t5r@@Gay^S<%iwULe|*&{G09f z{`+~GT3TAl^0;#x=Y`zhMA^KotgWBGB3EaEaE1@&e4Upkf%$VSExIj6U_~`FG{(5T zeEuw32y|RJgy;s0N^TucH%}n_rg< z?UN|a+n=lb;}E9JDdpm2{QT;XK*wY|W(J0cwTNl__paFQgLhS;f5K%4gxN-5_a1-* zdgn;^mC{c_!os)eLFB(FxB$TP!r^yi<qz?=JVpc*;!j?)!?30mPC&pwaEigls|;u zWc8ApK%njR@F9^S#=l2--aT0{N+o6G--$>kga>EkeUBAJ<2LUM~~mH0-FTZWM&e$jf`IQAXTQeH+X|)$Lz}Sp0Qau?Yzku#po; zH{8lOTqA@jfV?sMA|j`x-3g0cKKnW|^Jo<>huq1Nj|^bT`3@X-zLy%BWz7 z+}A0}SUx8ptiCG`7nAGeS7ZA&(Dk8 zk(Mgg`3P22CPCa*O=UHR`P1u{gS08bV`DvjVo&$7&hm?jiWX{LnN$CEFa-&^$IAO$ zJludMJ&K^*sBJH2WMm8<0K8IQCgr+8-S#`=@M9JFfrITZ0whw@+7+~19#(4vieBc4 z9Ir1@E-_l+Hw3ywK^?H+EO^K@J~$!?O+#$PMT@oq3VKxyjs2QdqU;F^Q)69RWdc@x#7_HD)%vt zw1++qZ3V0O=AMidUSsF%|y&yQ5e{65N1*ZsyJ7ta4p_ zefvLVhJ3;h7a1B3IYmVe16xIHgA>s;xHbFpfI1HzH~_s1=US3bSyNLGG)QAlb|Kej zw9iG>27OS75qo{Xl^%q^1#j?$;m?KjMakDKhcLy)2ZJQt zi=c-v+|78p3O<5?2deYUJPum{&ceHRU{4YTkLeXn6WhC?`RyHR>#|Fv(RQHk7t;Z` zWt=V#iR6>+C_+cT<|hcD-qaew|3TIRx6C~GR#vx}nWVH5a=$bOtp&~>?~CDWQBmEn z8c2wNB0*S`dz0y+D?ys&)Imy@rBm9m4RvwYV_1XK)CoYxiS^JtJ?KSz))9v~5}Al9 z%Yu(3CDe3^3Hz#Sh}(4T;>CTMpi^Z$qN8t=Y59kQ=>8~Xn^+Z^hz5iUn6aBI)6*)&&jCWqqS zNm*IS{6@-b(Q`;;kig@sds@w((a~{SUUJNqf35f)J71-P;9!KAN-8QF*RKcc%D$2h{k3<b>bJT}cyU-nxlq4J1^KvhW zK6Z89w<>XWS6A1sng=>$2MyX)i3adwR!<%~^ia6KvJpfF}O4#!o;a z7V^ZNdz1pzN-DCOWkV-UpGIYgz05I?uNlSQ`Q%~+qa>>ah7d<^7Sz>&z?Vtw>Fj(Y z*WabDYy1sUDIxRyNeES`6H`(m#Hfr=0PksVcD)+)!fQa3h7I8-gs<1!|Ln6|w7=~d zofxjRHf)idooF-VBc*sdpG4kg%aNvrYqHg*Yg zI77pB)Pt&<^WZ_HrsNQ?IA+nYH>={S43PKkiU&ADbVRO?RJ0!c^&k?8dP>b3#~pxN<0XBx_;d{5Mr7)Z{B>`La=1GT{0PTuv4c)#Ix)sF=W6-DwU_% zrE>m}T!f;`mIX?FcEW%AOT?)`P-WNijs5q9gOeu)o81%4J{K0k*F~uLv4q8%s)U4y z-iET^so7<}$=2qM7qMw+lK^cQkgFv5Stz`WP^NQr0r`}@Chkw}TuRu;I3C)L${CF|v}*zjF`i_{sA`W6QgKmE!V zWxz2Z9st*jIDxiwcD=wBTAT6iuMmzjKFn6j_xQe+A?dNLKKl z)02}I_;Q3+(JJ1@#a{JFFXeTu5k?o6GT4`5X|jZQc)o&KQh4PgH16m08Jmgw3F%z; zv-o$erL{~mtnIsx_I;U?Z{7ouU?(K zcUzSGGr@IjOs`gtecj;z(7D;%_PZ4|OpR*AeNti=5(Ys1kljip@-~SYTxI2WrAJbA zA&gNL+#kGcpx-#7!Od(l``NKQF%teBfV0`A!$U*t931LKv0p1HuzwfS)YQ(Ouk^TK zWb`-(-rER&85`5yO7L9!CJv^3Y+-hMX~`*VUI&@d>NBZ1kc=ZNcD_V=)J_K1A66Tj z2HcZi_P!w5b2U3jq?anh^18Ba-HOViqV6sSO4emec4-7;?(6=%jdNDMSV@E=c2k!p)9Dc@+BeY2uGPj4XenqJTg_`Y~b~gHUw>en2nR=4@U_ z;{mJYmw|zhlfK)|LNZzPcPHDpAY@XlCPUhI1|QZ09GBh|c+Yc6_rit9|Gm&D_#Tum zW&F+_LT(y;>+$vd&nODG&irpHad|*}Z_P8vOHMv4NL>&F0cN#_L(TzlYGgEVS>KNT zJ*;d{XCQ%%Z2ZqjMjlH5Na&$37Q8*1R-fkIg`#NtgbCeiC@D=l{3!D#Qyej69PX+1 z{%RLxLSXCi!#^-E_Wk=e9_gk^^VRXt^}|Zy!hP>p?cx;`B~2n747S_8#)Yn?xp`<{ zAjd0%u4PW_Q9(fgv~81ZBz>P1FeMZf6@`pr#m<0TC`_7$MDk@Uo1|q^pIV2?X^4YVbAwxY;7hqeY^e1OX`VsH`9vyrJ z@fb#kjb1|Cm-6l* zh~R1d^RjYETtixC&Y(9@F5JSDt)e=YZa-u=%) zEAK=|qAMvT7K;x^LC(=42g<_tQy-@OXBZB@as2QLv~IwO#`=m1bAA0||M>!l8*srv zkD>nht@=5wht@+QBd96MNlbiAJQA&qEOHcu*BWt{aQyF}1Ey&OPo_Qg^16VN{sWA( zPM&$k(ESXjx|26=@{E8BSuLDE9u7*+`?h1=|oAw$)P z4@c~b87D4syDY^DfO$c#sOA}@ET`^xe4U)_l7C=&KF456aQ6}y!-3wVU&+NH&Le06PaR~PQ&lXg8bWo0h|i(9v}*q5?UWJ=%G*;(jiWqb+c z)LsxnX~yBbbwp<1FW_Sz>1VGCGwsra4%?`+>H#?H$1UIlE8C$@I^&<~3yu*fj^x@e zUpAN$%|S(;1B3a8oZHiI_226$tbrH}ykz!vM^e=VHEHSltZVeUVMx-2hXzJQTC1wU z{$~eEfsmnc*2yqc45n)ALl&^l`ebZvjQrX_eG3cZT+WqhmLW(iDlYbogHn8OXsGtg zDH)ju$?Njo%J{jtQ3-P)E-8ZYW4|?J#nbTmwZaHa9~X5Ld+X&`D=)l@3YjAQhIlYM8&Gk9H~O}uqGBTud`FGyaq~5{ z2M#Uz3AbB`NEk^;<%1ir+oFq_FFhl)!O;5Q_U=F5D7WvxfpBil$zM+6?p?1#B^_h_ z=X}4Qoq~nM@4C}7jLfEhQlJ<64}IWz^!V|w2XKec=g2}kcOJKblyZD-tM=^=3VN@? zqNC+oIuTwV)et@nqR+A(HQX}xa&oQ)Ree_5ni?ARQ5|V~NR1+n?jl0Ov9B_sEvp@O zN5R4sq^@nh8!n%L-(Gge*GdAnA>Xc2Uu|KRf9mx7+??e+4!P_V+(hB$NKg!GatE89 z``2`k66x1cQGtobaQeun6VjY(%Ru>yluko1DA(npYmm~Hk&`p~{Xnl#2b_BQbL~Wy z-s&c>c31bp<85G;tTRf}E^P-&DxYI6LV3T82Y7_4Q$JA3%6TlJYSiD-H3N=EpqfuJ z^`e#*+v2&Fh6aD?9hPD4DEhO=d2YFfkQ{NJ{>G>P$qBQ>rcZKJW|`euh!1)vom7o4 z1IP*=3aYk83}3~#b?aNJSN~cFK*0wv2`E712^mGTvUGhC7M*~3Y6J^aumH{6i<|UJ+PL&BnWL-^n*z!9hVA7=llrbSE?9VnZB6a<_tEi;PmYFV1CtmsVHz zgDAcoD1ylgj!f@|?g{l#PJQ%MYdRS?E@}x=a&D$aC0KM1QMHOhsjn{Yrk&~x*hD5T zF#B?Ti5--!P%6lHs6=;UZvs~i1^&y9zFCKN$ffXP1pZ%mHYq6ynHp2PC&DK5^&kWq zZmURIlq*jDJCxeESAq;q2viN^LE2=p{AAUPdl@|!_2$jnX5+Sd`4jgW_qLa9K=va1 z;rM6dvAX!Pxi@(sRBqabH{sPIfANA8j!^Lc*PKK_a51XkdFfvtY~(^e2I&cSm0KMM zEoD12{f#?r`ux^A^yBC92gZWIO3G@0 zO%x0Z&u_d}1Vv?($GP$B;Z=#^D~2i-dM6lR^nEhvzs*JqVr33E=u<-HC$2EP*v#=4 z>Oxwr6B7ch3GpcgYj0a@-TMMT6OSgEJjl{4#v^)?ji@sEW+~DJ{=qEYZZgKe)5F8( zqZBUcUOQjPt_K7ow&??17>dnO2hlRp&(+7m{nDo`BJ5XjRV&P{qi;dOT`G z%eVFqzZM!2)%a=Vv;an%0@1-M)^RhEr}W)jU3)Y$NAID>>FMZ5F^a0*j*gV%qzBx~ zW#iUe?+S5DaY^@GoM76rkwFG02t%AlN(4{ ze=xax{RLb@kf0!Zt@0vu@8yXlxoTa#+6mD3biCOPse@nNRCTW?3rR{sYFWC=F1+Yt zTifdfu)FUC1p{xa(K?l40sAgnj%?A<=PB88#)6e(R2<8Ybbj2LN3E`G2e@i6r&0TdD95RH?kLP~|Ie{n z8sBLTejdQX9T2%U;0Ss@^3F?%&noV!Ow%?oIjFOVRrfwcf%S?>-Hb#T>>Ji3yJfCm zK&VlC$Bs6*7}Vtfk6Rqqs%~tIndr9?Mc%0j==$K;*mnbSb3WEC!MX-yeATnS zw+dt+7vWI^Y+<$m!Wl2uNm|$fsl^G^Dn<0T$mHI?A6}!onkm#6s_Q(!E1|jUUwis& z`qGi~`nH9Q=a3SgJwyie?%jd|s60`V9YCF>M+;72tRvT{ztK}>Db($9QgRdrH|x|_ zO>WNXmOgQ!P+MoRsLf+k&Fa)EA0KJvBsu0QB$uI;2*1leAixgAAO2H{-v^R!yR(q3 znyu0383Ktg)_S3E#hWTs+ri|JsiL_18X7K=1$}RH=mI|pATxsuxrJC?fB$oJp^n@G zB8155AP-NMT=oRB*It#Nr_uH(^HQbpmd2mW^3xR$uj-{e(xZi(Q}ooS6km|Twyq@z z@MXbGMpRvUops`2Zre`+5bR>_M=eIz47x=T`oR{c>9WcRB-y})wOK0Fn)h>_#R~c% zMvQ#(2D0|ISMsV*f@BV1d$gQ?7$iSqyXH_wQftOggEr2KYn{%WKmSIq!1_$Z{Xox_ zH+$?p+d=yfPNxjC9S**!_WSqB$g{W|ne3xgsv}IhIbAprdqdZQtGo;%3E}2#15$$- zVKgtkfRO>_f-^>&Vt^*q&BL?G!v z0?ym%c<%K8UvhkWP+gF}e*+pZP7@^&TwLAYnu2;$tNmP_-b%J?e|YyUh1uy-r`naZ zb#$(33Yk3W8} zLhsv`FI&9S<~AH<7ncY^+)zoU9e8hhT2P|UHAIx@rL9XttxkP;4n1P6VbcksLED>m zmOq==uD;R)4Dz+V9OtDvc)DIN`;i+U8Z~r!XXan}p*TV&LHnS8=v|cI z5@CVDOYZX}ln!3uyAJa4`MDl4Tv-qMXvc^7O9T1wF)`*q~`nWY3MtRByJ`=aZV1~5eJGtIWZy~PG!^?V|ur1b+G^c(#gCaUCtjmP{?&YOw7aa8sp z_?#jBBS)l17@h6lB$cKxHX4gI3NAhP~Q2{75;;w>^2H{#=S$V9!)qI>? z^l{(FNJ2GStf1)P6pdk8BlZes1oGKWuRce4hdcnN1luOM6cE5_cd+|tsE)E*fza7% zGQy@0MNoa9Z{ET}kJR$;oqX&am?OC$+y+{f;S>KJpEjG1vTsF1{?|;8b(;=Yr|O^}&xJZSWi#jQaZGJgp9dA3$tK;*S0PjZ2}Tpm2bf z*UZ>hwL|g?x+KFD!s_&00#wD_diml7#N)9RdKedqo*$g3p4jNS(LW>`7eLhM8jA$F z;Y6L1pfgKG_wfUUtq@)ON7+OA`$a1AY8Nh)vX#XKu_(_YgNn{YZ*SP%x<-#dI9#du9~DkR^-jv{Cq~3t zXHl+zkopPc+Wmcdgob;DknE3zI{GAhvt}OF2;B0V`hs6$?HLmPg%z_cnSB%-(`(~2 za4?1vb9a{%y)w;K^Zzk}xc0_IMxC_IV?Lob{hm)LD=1i6Sj?8GlR1SCke4PC5Mjo9 zN**3VFD#KZDyf;fO~&V)oXENEv9a8D(2HcLG<3neDd(lP}Q5NXzT3sD)WEke^<%dBK>U3X|1YsfqN>^s>PHp zLn{DNRs*V{prAM0{Mj75pp&!)zJLFYeb4TQWNXAp(f_jm-(@Mb$bCp%XL>TQ=60Yc zFUf@J?}-G*Hs-oKF17qgN1<^#ba^(kYdL-w7nfY)r;^i7V+>g!{Pl(fQz4fS2{CJv zwO3WGp>rBton`E}JmbCG(At`BH<+O?5BAVu$XL%uPP0Nde^xod8}*h$6-$E^-+R*> zCu8-{sZDL|@qfObiJm^}jbq;);`4(*lL7-k9f>>}vIX$B@D@M=bC1H<9$?e6H8P>j zZx#f14JHb*%bNU&BWY*ECeLk7A(nDSu zYlLRM>iVeZ`aM3LgS`j2acPGL9Z5Z1MWZ!GcOg=G0w{!6zv?XtYUMNGTNv+2*J6 z1&jEG(*iv;84C=}*CsekqN|@(vac&k7+YWKj92T^PtYV~X5{3||7=|ny5ooy(~90b zj8t^wCGwU{n|3rBdgspu=9dn&&J+nl2`Ple*5-8f3a=K%iIBeOO;SBoLueDhi)^m8S_mr2F^=8&Xx^+a6d0!GKuIf zrjCGT&+=RN&9VF&F_`bxo!s1tylZ~{%wPOz$r~sck~zSwH>;qa@Hn;a0ECj;PGO47 z?rF|{d}?kE05Y;yE9IfpuscXt!Rux}pLRO(>Wax@;pRV|?noy!SLOYpnilpNHf6d?lgf3eonz_l{pF$7 zCFQ=w-U#o^X?%m4xp~6oV+d_34(;aQ@ydbU?uwUM_$2jWFIPE?ZLezEN4BzsAGfQ? z6Sg~t+7*#%w@e5_7Fh%c&?m=fp@U<2iVYODuh6;u-hg2#uS2LP+J=w|t%GLz>eVJh z?Et9Z{rB9P#P}s+U^7I&p&T15P3~_BAv%J@;ksdF7XR*DA4+VXFC*JUS${5am>73< zoGd>|>+ELBQM2qxNh;27Z zZ2@={ff*c<^KoMZtLYZn>70A(6IvfLaubTo>|TvWj?k#CrU-j~+QX!>Q(9YOx>5tAD4u z7ZjQ3X3b1ZwLX07B*+kKG7xi5F`-J3sIrxoL+ z>%qj+m}&@goMO=-SEZh`X`Z|^HxSbg^vaNs&5+90^EI%iuW$C7*hs2bvu56dQ;!@2 zDQ&mq76ZS5EKq1&-m-d54irPV9BHZthkN3-PRey1g6oGj-sR%F+zR7R#zedK zax0o(mogRgdpTaYe7Rq(r&Ff*lI~Bkqy2@+3g;ys_5Dz8LiOa(8|Kzvq-2Z(fN770 zhVL%k&uZ|~l$`60i0Loq%fA|Il4?8N?Tm|R6kK;~@aMsjjy4G)4Ej(h0*`QY7fDRo zEy46>`a?H2W52SpB%2h$($Sesc6wmvBc~sUaFB+#p*LFAGX}wTRvwnSu!`P_{*F;*IGR$#A7F!&~zcTS? zzUAeD9djplUv33+TUX)ULNjpH1HHb-82crb7siP_rlMsnWagYQW$LsT-A;q*rzCR+ zhg_32`oh3}FtmOLH8yYA(s}YOpZtjN+(aMb{x~T%R|N{#Phjjtr2z8b;7Ag&h}T8w z{Cs=klmi6NK%pNL&Wwk|F=^sFoxJ7lh(66xjj0AzzmSG%90Qh2TljG!lm{~~hHtP2 zn*$!!43OvgW4k%^56LHDX-8i@L`VDbSXPAxSt_oE+%2{}?8smMG0c6o14SGM?J$?Q zYeR;{)8Y^ICg{P3)y)IOxobgZ-_n z?D_3GN<}*>Wdalj6~Sg*IKrdzwY=Q^m;2uDAEAB!QEUWi8GL3>%EWUJrL_eoF#A}z z_3eR0hMo!74Cjdmj(5Cz4!<9!J7Bz;(ayf+oD2&oD2lhWd(p3$9vknSQxC7VgQZ4} z2_3jC+w9jUhfgcIEkb4s2UZrmiljz#Nw0j1Q__20E$5s16XR{{d0ikK0Y0vzQ{CK6 zv{k(H0XbjfGp~7w?daHK!1;CG6k?R?# zyuutNbI*Sc z%R$WelO+pR`6&pkGB_t-8I^Di;AZhwFb=sL4ia*^Me+u z+8IuZUur6)jtdpGJ~8-Hi7+ZyTI)I&GM$Ks2m)*WlUoMAg3P~O5#pGy(^Fn$j~1CZ z%o!Ibj*73Gn`p?EcJ6F<@~^1hW*UMNpiR?T%8e1JsV|Dj2Noj7eLz(F$i91Z+bZQZ zwx7v3K^J(m(u``8uPF|-dP__-;Ne$|?;|^2`w+LNoJk4F4ciW+i!WT;Ikm@mX*^UT zKX6=V%a$#VgaVNHlN>Er9nqP z=&=1QP&nbu8=0x3fR$UCK-^Nd)7`N!e!|IpM0<G(U$a0t@!TTe5K5SE1JLxLG(_#G1+~DlZpQh|;MdcKwS-I>cKmEs zJ9loZPRrbV3gqw7aL&kDT2`6jA^Kf%m1`F|_e#;5!WkCVUx6D2IM_4bSaY9FvH?d9 zYBrRfpp+zks?j+7gFJ_0VSVD|&+Dt&-1mmOenMPc(5Cfz&dSCSw;u}y+z53{$03D? zwnpt?L%k&NWsdKoqOQXQ9d)cU@cDenRnKKDF;AfKBw^+;Uu}NjoF47annuP{=}yU2 z2Ce{hS1XMl=j7O5y?PZy;-LAnmHo(RAo@9*Gz;gAl@Uq8WcFKN=!|yRX-s{f;y?+< zZ{P21^GGUr1RMVFhTnuTg6s;{?V~;xFtIRRu?*5@6OGhtCxhvQM~*{2;u~9Df%O8i zgqBuAjU+$?83zVcF{y9}q;eRTS4uw1Sx5RJO}y+@bOrC5IJ|Ekbw|3p>v6WB^^H6I zdnmRY1A*je^A(L%FOnUyCT|>Exv+zmTW=N7S?|I;S|1)O1iVgjHKAj=Ob#>cZWCS1 z#&3nE_TUS!e4R&R{cF-qwHkN-SZ>NfF^h5fxR-DXx@}mY1711(e4~i%w$uwKa>R@E zzEv#N1qUsz!W0kz78@r_-<67Xh^mn-^>nA%D$@80VsQoB1!_Xm+{;ZuRs04KI>*Hx zSbH?7lD>Y$$6m|S3gy+w#uS2nKs-wwQR0=y;5*;l9DOFgJ&aB#-5`@_V&Pa+SXjbT z#L7C4R=a1?oJT)Up)VVkmj5|zZIqRrpz*7bM1`%M^Wr1`=G(|Yhhc3$#mBF+-DZ-BA`qyUruu6=IBfbncz&`}3Sad;{{9036ry~XjY8lVSfx{I zE%_bws*&eq$O=SldvnqWA9A4|X~^);bwx!Cg1bl^hRSPG*iRv1V^u;XY*rXBhgw!I}2T8Z7|FP ziVz%k6!au5?ZMH-P`fPs533qWaBr&RAUxeB<)kf8URt`2h9+X|#0dn|FZbrSaP9TJ zPb;J6(*DV(BPn-cSjADtGpRbq=VgYuNc?n|@N?|S zvBC14Qx~9-*oF%Z`50WqirUC(wC#A~5nRYq(8QzZ7X%cDMo*uPK4b>b*7fV%ustcs z$(UY=_{k?QP;qY(I98M`0gl3RYX({1dy&jU;r40t;(55QwrOu;^gwzL)E}bm#l<(qCr(H z#nqduJe`(_&svkj7d^Bd+e1qX?09`>$5dD9qDzBckfft#kf{(CJ`4REka~pi!8t`P z!Ped$TBtl27W56mu)+*)@6#yI!e1yTm(nkA!IThMbmA>}x)V2!4FiWB(eFaRpFC#C zpd|ptajadl#uME(K0a9Iv%$wk38{|$@9(VEZikE6woM#^POyqV#SkH&^5YB82G#;y zXE62jA^|Z!dtm!v9I{P*Btyg!+@nnpP3oLWUfupVx^MfEHd3SgMHyZ>Vh(Krt z3|Z_fERwA4Z8S7~Al<~Lo%#A&ucnLc*|Q&xSZUsGN}-jX4{B)yp7<;*Q?!#*7sjFM zJ1aT<

znWJ;I3&2Gny@wyM4+^vS2G`4H8#6-3VS~W2_8eZhotRA7MTKoDPu>(2) zv9FJ>*pIb^B@hMilf}75R*7Cu$T%1#sVy+E-FroUZS1u%`RJk>%i`b66Ir?)A`k0?a z1-|tuI9$>f=qtd7RjE=Sv5d@wL2ouvMXA0Tcx><5$bSu3T^ zcPolo5AM@i-5@+0Sp5P?6Gv_C2`F&6xT0G61}6vLpQbR#%dLUAVC{<<5~WGFNBvBfx;E2ci4W21ru-q^I>b0 zt%V*2pblyls&Jz5{mu4OaUAy<5;-0@ieO=cR#5HMP4kwI9DN}d)kwMi5cDmA7)nZI z*i^*5s!>X>#BGzOF0e?UkRGQEao6=P{-`HFnJ6atpj}csar#BAdY|r;#-Jndn{5Ti zy-Xv!XO7!4helGr`@lN@Yyr=r6r&lftl&+cP7$R=ztw@Jtdy>lYl&17uaJ2{p(|k+ zxA-KD01KQfzotsc+3T#@qNbsd1<@q#Ldh;J>HN0djF^YMBmq8rhp9TINf~VVyU)=> zRE1<^X!2bp@8KPwj5`lGI^x2vu3(IP#r+2( z&&@pm7>^VJ1ZiIo;GI5=FSju=>c_Rhqy<<5J4h|j1qbE})eT&`ccVSM6m%;1S_BX< z6;SWEKY7vu2nl43`$$H(%!7GaEx95G4j3WwW)m`beQO^7J{~KMD-9KuHZ}>9wqEk; zu(GgVj>#`s@DAFUsC0x-cowEU@W>4nPzDBQgL<+Aoggo`O5R5Js2a6q?OG$LJIEr? zQB!XfvmQi?E?A(hkrAoOh15LK^{Xaerwm!LQH4NQF9!!Zq$9v2;yVmjviQ}_>SEt#(NoTAofNI|Fifh%8*j10x`7<<&bBUr1#^w zM|vL-W({@^lhB0r?1@ZGJ)cf8;F>vIeT_ff8N#Su>Ne8K8Kp2G*q;^K zy=ng5F1VX#>tt5X&d(FYlJWJ&V2MaiRM<(13JUk`-zOe`&jUH`%Fy=q|N8(?+#(NZ zai#;GKj*A3fuSLx_8=@FJp3}kC2&JyuMNM{*9!{?2|@V?Ym7WRP%kFTp`tZ6cL8Y% zUtiza+S;+V>nq={7_?`&t#=qm{!s_oTA&XP^t( z#_HD;DxOa`>JMD-EqbYG2SEeIuC187JA1;3?7&Rgfe} zV9UzeLc?$i_#RCZKo2ua@3HHcm@?ekMmO2U58${%`r%JP{UR+5mDn|oSnxN*)>2=> z%&IXPyXhTM54(E-(;y?wx5%$<$!Xe8(x)Vh2#J(_#P*UVnvUadt@G!1YY9JvqabYy zww?g1KG2evBc9I627HET$Iz-^VyK|Q%+0x0!s-F-08+v7P9gzx`?jwm@$m(V03Ud+ z%Mipawy!b2Bpm?=YWP+nZ3Kr z0A8?Q4QExu8_}5>(y5M?n

u9dVkFF(72iOvlhb5QJcml(TMn=IX;6DO8t`38;T} zce_p7UCkDPS^ekV_B8x-9EtJLc80MTVIlR=@UT=+o31KF&=CtmPU-8jqPWoq90744r<=( znh&wKx$GAzB$s*ck&oW^wk@m3zB>VU`Lr1})TdUt@ao_^!Y%3aloNP=?Pp*)_7;-W ze2PCb5?_f&1q3A>m%ac_7`fc!y=XkLNc+z=;2dLtcKfyLAwG0?_f##+S!YjA`DDxL ze5CAaJplU^)r9Hch7fvRl~z+USBNhuaJ|rMj1cZ6LO4XdU6{SYpce(JiZoy{8-BoX z!=E1rer`eS4{jD7<6HHsqZYr=>lt+q3ysDJzl+M;IGoU?ic{S&LolMj583DPC?=HO zuOxwt4;>pEc+gO9G04%Yk9j{d&z6Zj;)>LjaP{)H@ zll5wxg=NBI<1DBW3xd$=Iqb^W2sw#`(NDR%exw@unAoWcCN%gBHf5=%3yoknsNbTw z^xs!Ckb`6a@cinpawUI$g4$SlXy|_*YVycY5Z=jVt}oiQHGgr)Jh@7z_TXhb@@Z7K zUNb&|2~=>A??f@BQ*-g>x}*ebC^z2M7Puuy(D*yCc`Xpj8yg5XwqQo+_JiUe-gG)5($O5W5&ODY53ihL#*L^`GdZAo~qlsxR$t z5I%H>m>v!yt=f7Jm=8@%ukIQ^JP}}B1($7jA@zjj5c+u8+;=S^r*aVEM-jfxQ5!sj z|39gq@Z8I4(|qHx?NkFkK|uuN{c7g$#FY?V-#t0F7)?}!=^&9B_7fN3&gS_L_)0ga zg`FcUn3nXTHyy*b3H247_$bLdVF4LL^yYcw?oN*$HMzQEWhL&gm&^If)5{oLEGut` zw}%Z6v{z{au9`u{cF#(e^D|Q>)b#UcI1spOjzaW7eT6_F)AN3(C1|_x(QujeceY)&!?dn84YWRT`dUv$0)5FMBL0h zK#=Xtt@a}lKR%9bIqDP^PYD4-FE-OgFT_prb%h6Jo|Z%_C$>d+OD2y6X#2+f0#KjWtvGAs4-gNO7ES{(W)Ora)&KM2{8JJZ zzt)T_At&~j+E;C+KQ>`Z=w+J84@koj5gfd0G#*RfRmX>E!@V5|HU83cnA`T` zO^rrqB@*8#H9wewK)J3EV?-j+HF&Pv@~bU+;PPnOp*FgYAS{!;S%^%{>~esoBP!F# zv)ybhU+$^JY2sW<$FE9CQ0}xy+zI0^WY*L>Jj^TIsuEn6pvR|$JAYL4KX#;yy@=fT zE;{6w@logzV*p3ypm-=SE+xhQqt7(@%UKW94H2i{NQL+=R)^o8nT=>+o@tnQO{&5v z6CB&_QnffvH$$+6Cb6F&GYr1dLQi`VT{ASHiNDf{8z?^2aK*?yKRwn#*8>|)jq@HwCFxaLk&D?&@WiTn7V@oIw1fdmL}Yy8U{ z1nba3CbPrkL=Y4k+CfDFdPFtZ4ieIkmkF1^_Qe=s$a`)Kl0h)KOygfb@_Vx!aqUUu z%N@NJgCMgce`vB^Pu&#FjH6yMEXiaN0=$@4K0A5Q4MQz00B9@^)Mf4uPnIJxY}|cU z^fVjdb;Qy-!JelLenP=ea*J^ETFD!-gZ5~}9CA>MWmPK<=GfvfXx1u5a14HZ)wS(n zT=ZnR%oeoahzKBoO9s*C1gd|7sH$Qz5PUc7cE^b-#ALrsBb>pzN&YTm$o&ClU0!Da zYtL5F@j8y^_l%ee$xrb6fQA~hB+^ZeKQmwd{c_x5EwU?uwAdIhL$O^5R)`6{I>J|g zWUdX|B!W8=i{WjdJ2FuPoZR?2G4W|nSg6ySAx5H^Tz!hN(p_hvUitA+l;+nCC zaCa}7&E}yniVMj5UBuS-(e6s?2zmaAunl3Zz#JVIyeElF%o$0q)+Z&&mX>XBVRWyHqNNiCe=afZFp*lYOtF{25) z3W&=WJnuam^Ou+G(^+c<&dzMu%;7M4_kH@4QViXG1FCw~{r|N0mSIt^?Yl6FOGQCM z6m%$+1twYx6$ zz!OZsX50V$_qJUYjT6Qb6*k-@6bPQTj={uTsp?u7t&sokx~|s4NvNEg{$kjeq4VO4LP;HNk{<0Ac8diTXA; z33uByOlv$ItJWTcO!vIxK(^7sSo@n0*E5k zJkJ#3FYEjcCWQe}Cngl)HNNpb!&yu{#vu5_>GeK4z>NSgy97HciMqPgcV~FEuHwN6 z0!%{{O--+D?$>g8e$!b@sbC}oJCC5$IS%VZ555Tuou{T&?rmuRy^Of3Mj&dZxhyP> zHkAkwzw@u&9y_f{?At?{IEY)9=EjWZ37i`FZ`9%JbH>n7w-#`Wjr4SQ@24a*HVagt^ zdW!<=l){w4{GTM=E~lcZ>bP#_e1I24+RmUqpJn2b5FQ05 zS7nP>C5yfz!knU@$tds5{SGM#P#MrLOwg(2xKj~evxF#Q6m%mjwo$!6Ln8wD65Tj3 zps5;DaFIZ?sa5aDDDVs`v>H&`bc4iE{p38!YYaUL~*C*L?OPN=u>r0t!v@) z=H~K_IrJ|ih~0vnZ=Q{V1JCloRJ6?+v8&I)?7ru3cOUhp6@elgu$C2XC<3kf^Su?d z;2US8;1=+g?asZx1u+t(gon7Qg4S>c#!UABOs}N=|0{jM1K*4SiJDsuvuB56t z5Oq7HB~C-Jj;0AFRx#GbLIB&MX<-rk(W8C4mK|A|{6&!1A*za*va-sbghxT5%hdm~ zbr8B0PscQD>#4{q85AlIc>|!|8MJ}(dEc<9rJ*P2_yafxR$JMz)O7(G6~?N@Q!-_z zdcae8?d}F&^~5R43rsGWFCKU{GYeZ6~rq8KJ%_wF1Y9r{p{Do&;#8x zFuwAc|isxSa4$LQ8zLnW0GYph;Q7UsQd}&I-AIrKP`vF;X zv$eFkS`Zp79Wv9$BWO}0j+pX`J?yhjQQQZWAvjRt|a9!L@lh!w6)P0ShY}h@?=(T67nygqz2OlZL zgzeb{9pJ9wbcTvX<1FfzL83ZajXNwayQ^E_rs|YS2k)Ox@C-HK=syRZIxnY`%(Sa+ z^F|m~!pq(?sv3cNUNxFZm-t-1Rz>XeW*g`__Xh5|C*Ax6@a|76FH!yC91i*Ne6-;R zEK?9^xVgHqF+uTTj}oLvm1+?pC;=Nw&4p;#3EMLYd?BmmMfZc9Y1MIeS^{{{tR9ED z0+huz-G>LD6hF7vhNUIAxw@_*Vg551WEWg}xPD-I zph9k!gO2P)U7evWS5Rwy6qt3*jd_ov^VF7V>E)a*5EL%*OR4cQ)k;dv6{oXCa*}@i z1~95z9tS=KN`7cIe+m-oQliO#l_!+gXdE29SamY^2yHMsteTMldCSl(_X~VZ0p?Eg z7LkAoSk_*Ug>FI{`%7AMm&(H04RNd~FDHVl6le`tRA2cr_gNFo@Ii+(lRS8)7RiR) z94#W2;Ml=>R>=nsR`3etec`%8a;Bo|$SU}6q%gFWhLRYC5q_NC&xUO9>R9!QeTdydJyAK`F7An zDR)CSUn(q`+<-8jIEVK=cH#*E90gK7=-f zcc%0pFb~b!!b7pbr-0}(w(pj*x`*1IQ#08n^(p?W&{D{<@z>LHWpAAfml=I*lDJTG zuV*+S_5{c_788TGJcccIsp4(MAyEi4X;wQGg(^;ngt`Xnyv0;?cU1TZR@FCz)JZSn z%$Ckw+fAURQ(XR}V3{f(`2b}`YATvhrxF_M;oxNY^Wx#?MvrL;!7Qewjr(6Za7Za<@Uj#pVb?Qa&w4*^T$Q03<;{)1V8sfQQbmoWidHf_8#XtJx* zkmfel7;lgt1GQ6}60Mf`^vb}cdJJpM#89q2siv?TK7kzpjU>$FIQ3kfE+zKM}ZPtNB&8$xi&z)C@Bj# zY%XZ2s!E==87%Fn9g?K5<`C+*VTwDSC7-jT17*hz^_%l6L%nEbE>gng?Gi?}4w$tc z(iWU^Pw%W5;9kybeh$Yjr0ztT_xjYEOIx>;G&E+HaAFqy4LfxESoXw~$Uv;PeUO7- zY#1&p!`G7qxjHjw+uwOIq-%uX;oIf@7TFQW(1h2!U3B3?bN1>GAis1!3gZd@cgA(5 z+j7<3$Vh4$gEb0srQjg9a>vdsJ3b*g5Bu)AH~XvR4GsR(=p=9Ha{1^xGMY18AW$ub2&OXq=jJD?YxP;z{+GdW7#LvwDkbUUEaHHq~-?fH?>4K(3sgDZ9wRG|l7wm5i z_66!6%oSL4)>&AyLZsH9*@r&v8!4sG*8vMC54p>R?WckMY8luAfC|RGsjPq{9|1+? zupGBV&&`hD%B^8{^f%DK%F(eRh?Vbj2lR`%2S^;yqrjsH6LqqYQ&@G~3mjQS@(VDH zzu#vVv}%k1w6MQBez?yW3^4W*o-6n;tbr26&loh=lOYTt)3NS>d{R6~l;I^;h>%UK z9{`XbLz~}<4&lVJfR4SAC_~;1+3P$7g5q9iE{5;0vig+#K`@RHiYE!zc;9N@^2$bv zI6I{wF}FMz=k?#zW7elz)wJcpT4QkGvzRMzSh!8PwXH1^T%2v&+M32ua4=-vo#+a;o*EgzOTo__dULK$y7uDJy0H)pRrF>W`XDgID>q}8Y z=$LtbW6?E%q13*fufOZ^y3z5fa?EM2rEG>y$Gn?CC^Nd5>cnswLhM7)>;pCZ1P5E^ zybl%<>qKb=A{t=&gpWrKRX;iIezMg6;EZ1gek&Qm7EtY)^jMveozw#*MWECz^O%j~ z)=0G7$d18P-SU(;k!NTS^6MYlVq1?-HHwO;t-QYB#x^Da5Om9xb?! zikIQmI)l$4?OLACQ&U8ko;Fk8n(6UYQ;_I%vw6x46Fe5`Ipi0|M@C9YoWjA^IjPuY zAaO`J%wyv%UObxLYNDz&=7#D({E%L9T2+>CZbaQodcBgk#H)QD@`Yh+2%pV%yV&Tu zF&hi=LWRlahlif)DIb2#?(gCg6<><@rN5-!TI#oI;ZX1PDfZgOVPW@WbfKoyHbsIe zgRooQps@U$&Mn9KON*Mg7pq5-AVBK<(A}CMePx(vA{TeZqfgJxLby`c_spDP-1p$0 zl4C}s@jAp9_yE8z@U9Y=Y1oVvq-A8}L=xv77P`|zs;dv<#aF#TicCbyXtVLnxbvuwKivNG&)+Z< zjE|iq^`{!hi}qZQ>~t#?QFICEec|Dd4O5OOJT2xi(BBq9n69#;URAnH+6~9oIO{?Z zFLX{><+HhAr((KYudAZ@ z(7W}rimqw~oYE#&XLgjEbLvO-nAY~6k-Ady-or_AF0-91j6P0F-6GG&?&3swW|ED0 z#wHoe&Nk^3csdK?CoPgb3RUq6L?%+-=BG8$iH>wt$~2k}YpE2g$1fhI+=$_4`!vkF zUo){;MiGE=O`r@^xCEhA>#Rs`iK3#;g*?+E%Q_Y=ugB;FEmuf(`8qX7V!T&RcbW-e zf3Tj4`Q~VBGm>0W;t*J7`pttqNoseMa?5zFX^K3Z7l4Wj^K+9k^&zbel@+BK-i6}3 zyB%etK8_Z&9G{ox4L6o%7J_n^qM*8ij>XasWtR4B`4g2H^F4Py=fS=wtX^Fi;?-x0 zG~0N1u637;N)DY)YG`Rl32)~g_m-mEW65+-HyDBT7ajT~nK3Eh#YG3Sc+%I!f#RPB zY)_qRs?9mVf`Q2Q7qRm^^PK%D$ph_wh|4J6WyjVE+?)W)dvf*<870& z%sWq`4q8$h9T&H?!In%@jKeDw5_j#F5-8~O3tp+dzI{;Rn$^%3o~e~5$B}A#eg4Tb z4x2G&`~cy#W+h@$%RHn)N=49tWpHiU?HLu}r8B|9@ulUFf$x#nmv73=C_GSZG#3!s zFU2YhTF@8JFFh5JtdP8CvaUM}*K?uU>oE?-AA>zURVi4>$gF-naGW;XFm~lwbi#P; zZp?9VqJIjytBKZoTjg=ZxPuDilUF{-3!XxnF~6UxGG?$6K-ZIwT;^OF)5?dXoG0=N zJ#9AyylBSa4^RaGT*yWN)cfVgVd2Uh%p^)R0Uynmof`6EEb*$joQVv&vBGcQy7m`F z>Z*`9TUwg)Z;!Xr|86IYH6exXHCdYfdZ43TJBH7~eH~vi&{#>*#CB zN7>SJ)XL}cxB}+psy1f{EzuL~`bC)bTejP|-*DA{MVW1=Eywcn2T3F6^4vsC zxhU1`XS^)lio>rVmbVJy;&MT0pv0M34Lgc^2QtdN^Pn?WInekDgRmn!Q0|b-U11c{vS& zBN=G43$B(4AvbPc(iU&=91SaSax{8#KFw9(n(! zWoxJ!mDkzY`CGJ%8%2*ED2D`lsBkRLchwkO)75zLEvI3U5(B^o>Lp->wCerqRs3w0 zG&xFFX}MIaZnlY$xBLZ#y{RQ%J!X%QbC)+KtV=KO5bwy>T=ams`kCxiwH%~awZ`lg zXO3!{{mF~tIYV|V*@G%22I-b zg@?}(e{t#}t}kuP_FyGMV z1jOT%FJ;p-qU%I`fhBHwgQ~rlBSO>}`mDXR3C<qdwW120!-G$DFy&L zkyCSd4HTST5scY&;XDE9f(eNCif6A(z0n4o%qyN=S`)^&?0|I2N`?)-ImwRTYPPP5 z^{dAeCV|T;bVDwTLohRe=3uBQ9cV_xrW@$wJRQ1q!3bQ@kb-x(Hm?4O7Hr9sIIxmdkr z+s;0ZEAp;qCK|_3<)?0DdNCdGcBc!VLE>Rgy_>u8gUVAsN5DCPnkJD(A@=TYX*~zD z`ujd-I!}#+Z06s?2f#Ef@la3Yng7@#;mT2!Pa)EOWpiGEpO;R}GNUwHh-vx<_=^$*r@ZmWIHZ${4UGbpk8 zm8Qxt5d!H&J`D@nNZ7pb{pIc??S~vjb#9Z`KAAh%MvYxHh&I$kEYUygYoq%VCn_4O zT^1i_>))0uMoP7c2TXIl+OsQ?G$)yx+B5Pw0P@g~j!~FCY@B{H-Zq%_ERTx|;;`jN zF)xhIRE zG@R!V1R6h6qriv!lK7$g!-u!{nO}y2uB#BxB(tuRj#Ksim#ArIqy`>?FeVBGBrZ4~ z!d51j0<4;RfsF+PPX`?+pZKc6LB*gLaZ4XWkznyqyjTN1*}E@h3=_5a&AYGd-YvH5 zH$e5|IPY!SBCnumfCzzvua&DO29|#7Lj!m@#uMLkI-aXjSV{$4WX+)1ZbmkROEsF0 z_o<6w)5Vh>Vg1`^#ggiqLoOkyBm}*E-lB5~uz5BB6QMhG(ak0u$*aX_MfdQ+k+$L- zsV_5JwCJQsWd%^}@86%~&(zE)SB?~LO?2t3iz%heTjo^`e`29>ym6YUwN)f(7n-W# zTrblWj}2>y1&dK}_1#3Yv4h>0ungr3IAi+}DT)}=G~To(|L%zkn+oWRrRXnQSE?AS zt^}cG9k3I}+k4P_DpCO(U7~x(=_&scBc+4r)flt|JyXMSv_9i2r zV?#6e@sqTOXg`OorNAdv+g%qjzZvSz%R(i(O<#HypWG4L@U4B<(L#l8)mhK;-t&hm z4k1Ic%D;NCI2uB^!*0|RUvo{ zA`sYxL45({aj)~sql@WGnrnT%-)mDVlAP0x^W5jFkFLVB$R)N;vlqfB^LQ7Xzif=J z;FA+lqJ{%{9;+Gi&>pG2C@Fg3MN6K|!mzOU4p=*C*k^#$Y@Y2Zoj}?lpb&6Bj7A$S z^_^GLQQd z5g#{ohMq`3|IT3j3#@Jk&vZe9x)c6tO|$ALwyB!iOl^QC`r+IRbq^}a_V!#gtUkng zY88=DkcDAs`e_JCqv4;{`oi&)izfI}j;*mRsx8}2V2hBZpC{{U(^4gW$5m;F8Vgkm zGfgz3Uw3|nxGCLsmg?NS4qz*|`5n3Ld6ZhSHIr5F(pQ4Wv(dh)qBMGMzQjj#_La!_ zBCQ{oJx@+=FPY|m?h=~;^bj{M^p(8*Ctv&p)ey;Z2O=nZooo3f6T-E;VtZqwhxViR zdp0dB0?@PRJS>bnfwvC}lW@LWZN9CcwB!8TH(izU!%gA~vT8SV6U~$OtbVDoD>*nX z#(U|#Zpq_fdTSjR+?G>Z2k^te4{jaP&!P@1FCdq1a3^R;2zAPx>q_PwH7wuGq85wdC#?Ow}&CMdLSch)!`?Xv)c@ z0icgV=vuPZ$S`27CAVsi@kj$t9iP`vPgv6|baPr)YO=S_FLJNgP-F(@w(b>a`o6P- zsX9(U z_w7prshh}n8f3(|2Q}4+!04%GM^xR#87(v^@*`BHTFP9R=8Cf%O_C_s=M3!OSFIG6 zECZSvQ0;xROM-pF#>RL)8;|0Do?zW#Ms(V@L2?5yMVB{|X&9|J0R~=4Q=*;kL0u?Q zqBGXs5`OX5e2wmyg29Nj&eHBcQuBoa7d1wLPs^Q(Bhd1o`vFbqwu|-e$~G4M8~OT_ zKGg3JxJ;JF$cl`m4);D@X5ROiS#})rR_w@VIc%DaB_j39-~G&qXr|SLAXE@zy1Owa zn{l8$pH{?zs6ii&H$y2XS>@tU(-%Gf0fMY21j=S$vsHK529e69rhgmD-nzHa#Z61- z{1BWYc7p*!D3>nbOxpKti(x2#X5XFO9Ky=|1Gj6Sdq;opBKFrpzn0Y%#Thv%!{T}j z_v>yu&w)sa_oq(JR&_nr6~ZAyvtwiA70QeQg@0h0!m-+3Ri2CHJ%h&t#+3u;AUH8( z(+JQR4;AI#)VZi*;SoC!zhsWKU?d<*ze^>=J#b@&gc{+oz;|JH>S@I6(I~1vt#}}c zKQqT&PVC|H51D*g=t~vOloU803dXNYuWvNQ{J{8x_X*o5^gZ_DvSrK5nY{hsFCn~k zN!`N3pIQracWpl-J->IK4O4E*f!o3WA4B+QR_Q}RK%A%^s^j<#*{fD%m%Sf7dzM^$ zLFG~Qj*t?z zZZAEl=C-_31EeEJTp&Db5-RqP)n#`aLDiI;2hc<E>`+0AnXu{?O<5T$A zS^0TU=WG>8!>M2mC<#kZdFEx41!TBj!2I^5$&(D0ST+=lKmVc>u*bOazAoYp?w43_ zeDEAWz^d*1Wb;J&JfDBPd6@mpk!(I1& z(_0BT2Fptka(qc4lP{xU;lu;K)iJ>7pC*M2H)h+;w6v&?^(MP>n>1`PI(h7*d~+W zq3B09PJC5tGg{Epc#<&HmrT_gdPCh`a0ryFH8zIRs+)Bfqn*x&SXSZvQn!o8s|w3f zJ&7TnnutM%b461uyU1Dzd4bXraOlE(OPMzd&j$kN_-FPcH{~A*UDk}yr_5XSK0RrE z<$`^i)|F_!%xMlfHhj$Os->pS0G4cim?6lqJwQI;*8q?tWivTwcu-xIy38e^r`P7^ z{h}CX?tyGE9r>WaEkAfSBO#9VpybE^BgaXnKc^9noo%!iZt>k5K#e=yRXkIEPYZv! zAkn2)ygi-Kd;t5Isd~6Lth3N?Jrs>))k!QYw0_e|Z>TGBL`^AikZcgL5B7Prt-(lP zx1sr!MUQdD&4I!^>j@KAoPj;@K&~4HfM#|#I~$H{^MTy6N578sBkoK88G(T!w{_*+ z$vIRz3__)(s0U@E)!{U#KC}CkYH|nP(kPHcgWRs!Yh@Tv;hu)3OH$I?d{EFvA!1~4 z{_mq~fg$oX1^4h-nQ6b;()w~+H?Tj7>iESxt6f8iA1@m929BghHg0Uc0T=BkPa|*Q zxiA70!hEOjdWf9Ph_;SSYqd>lzy(4lzcNSaIzz}n$|a$d9>;G`oFrvoX*kK_Qp>ye zTS}!FaJP7M?c;`ruy%mLVYVR7KHIb!U7t!b-V_m_Twwao$*u9FP^&6;-c?4%|P*;1%L4No)SXtt6tDD|%1e>9jHi;|vt6AoeP&^V2sqFZbq7QXE zb;5J?TmIfh3jYFR;Z(q0a1dj{R(A0lr?=jtmtRQz`|M)d+aun-HcPDJZB?*^+NRy}P+sJ8+j@Z9!HuaZSslhPwEMyl`h^oc(Hy-nOZAjBWdt zD?r%0iXU3XSrzGX%5#uqa;h5CglwHMC@MeSU4luMd`a-!mXw4?ty)kC^Vp|Nt$(v= z5dXA`dBhzP@(p&f*w{Zr%*2@QbT?3IurbTLBSULQ7>>jU&UqG{kdMA;9tw0HTEpHS zwN=|Ae7~o{FWP&DP!EMI@&2&P;ZE%OmL>Qb4}ZS3h)Er-Ic|}3NkoCc`gTcjj6{-L zmSFYyI~ie$FSrlsx*@VE7Cflx8lIz|OsoRz{gayK@tu|6t*hOjnI zdsFVIRFVg8E2A61dq(qz^erAI{v{RPWeJ38#v}w`7=4?w_2PLNj`arse2!%f<-qvj z-|kL$v>;po_+T~7AIq)?p|9$irq;jMBK%OaZ`xFwio|PFY+Z9O4t57A-_QDh;C|R6 zcVMv|Y06@^D&GGyBEsnYjRFY?71E-|kmY>*TsJ8{rfk(p|3c0#4@B!KJ{q`ZCO*`K zS4ptF8PHcN?nbtF>7v@XJe${zYtKqJ`^#?R*_g?^o1A8|@D?qeIPY%RARK^d$oz20 zgS${%_uhUzWW0|vvbwU>S4cO9cE)E5#VrPys)M#bSewNg%lIG^vWdrm#pXFdXUmZN zgiAz6kARlOzLN6tsN=$W3YW4Lf`Z0dFe8;RsvX<+y||j;RPD&gsIV#`xNs|MKJRk# zdt8kBpgoH8AQSm3*uJLz8y*Ini!9XA*>~luxzrS1N4wpW^heNf;9-w~8Z#pz)q14Z z+p*kR*KVJNfxx}BQzH5gfofYdDLbsAm^UgC^nM@l>Oc|PX9MUF28oIk;r$R)Sd=KgsrPI_ zGoxZNclbpAD))KS?a(sw1Ry+2T8%chvfT6EHr#P|d{`;uO6m#ABZCB6F6`1%GFjiB z(zH;_v&kBDI~_8MW~cI_N%LI>sasA(=a^1Cd}NxE^ZNr5eVKauGS9L`Bi*U|XlJ+$ zbU-F0Au6Ak6bg(3nF7VI7ia~#3Me-o&J;X3Gt-jt=-={Lwn5vmA;cD(1tM`Y_pRICp7%H^gA0E2B zYD*~O18&x33;5R^S67^Ddqfa&Z6~Pk&evX0YRb}BADr9P>jsg}!SFTvXHZn|O7w$v z7-S*qOJgbKg2gucj%z{ZHRABt<2vsO>6NrfYM=Oaw3}FzOE2b zy8~Y$HZ1Iu<-o3Rb!?R4)gyX=|6ZV>w*#~pKDWczj&Y~MOi#!7ne$8vp3Og6F^SS4zZHJ46N{M?y5SqYq+81vlVAbo$9 zNB})Sf$VcUZ_<^j%nACtExB=r}17LTC}A${aD*(kF$ESFQJjA6|~AFo?ph zOvsGyd_thx>Dq)MuLxdDTIN9A2APMX?k-srs7UM(^SK(|(2?E&IlO}>TsMA>LZ;l% z3QIF3peP`n&|i<44; z@;EqyvA>{9CDiSvwQW+NX)(gc2wA z^@*h!Z9r&26HszC@HFUQpT#!c{cyJ0xy_oL=O?TxNT5E_pB)M0j(~GE4?#{1rXswY zL)eC*j+XjW$ZF3^Nyqi(eVX|4gOo5nK63-j+F5k2>bk%c`A2f`Ih-hujb|S?mPoAV zVuIR>YnO(q1X-h6V@>(d7HVJhL1q1-uBW}P1PT`jta5?FJcZ(@%sM|%JK~oDN4HMh zqf^|38DMVX?vp5K$8|(Nxe)f_qm`YO<1yqA2Hlg#Gj*Ebsj}GbWNP z81n?`8jS9Q1)LYUGAc&e#q7U}kKt=|N*{BoCxkfec5kG=+?}^Vxw-jzw{~A0hg+yE zJil}pCE$8hSHjhG2UHUxRppV<1^#^QxKA=rf2;H36cDc-M^C__L=Iyhse7wlNCrTq z(cPW4m2YDCh+m#WwG6#9 z7?td<7WP$DI4|`n9JY@vRtIN+&g(xl%&9bquLTI_`F{7t5+<$!N6PU#mnAlU4Xf4Xh zh#<98j0V)df9?zprk<@IAIC2aF-RQB2!|!glTox^{ueoH|6=NL&*lCQbBdYDP0jnv zxH7}&-J5mWl(0=V*z$=cQ9fO+lnMvYL5Wq9WKa=qJIPlLo)1{-=zJle_d^w$0VQEl zoMBj!=Ue~wiT9OHlAS?TxJ|-?I!>9{Z%xrNHb4|-v46;NogB##^6OXjM;Pxk!uSJM!O4e^Nw>LF7-2TXp(##Vg^eB5@~S9 zA(0moV!-v^ihPQXyxD&Sp$%tvRnN*k8KNX?qFkVS9!Ip1A}LuB4U*>6NsIfEn4fsn z6la93OI0rc4)!e{$#dF2)8>Gq+*dxEXXCq!0XGNI+HQbkQpkfc zo(9khl4~CL2#gwx_UMqAU39p zeK}Z_F}?kwgN<9^Cr$eopko+IY?gnl8oF<-^xWAETpZY(9YTFCy$xV!+ue_xf+T}E ze;)K7^}(jsEq;x<0EXmh`imq>od4cJQ-O=~e5W^VYgf8-43ayNY!R(ONFMw8$jdU* z&ZlyZ*A5kp0}NwGPu_m=UkC(7a4d=Wo8-%B+D1TsFOKqm(Nw2gG%Je{EMn@eUWV2% zj7w9=NBPc+x~u;_?*9z#=*g(6V!@Ek){5(X$iFuL{?S}8FMvOOPjdb95&h?9NJxk? z&_v4z#uNX)p(NtCzR($%pM8-t(NbcMoqW;$PqY9+)S2+VR^mg5(A) cQ-d!(mTNK!x4jedyNIu3Bo!WHi|f7mU%DK;g8%>k literal 308039 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

PD#G?#b>~PaA z9Lec)1wbTaGVl^+MzU>~JhjN%^Dy1Q5~gI1-a9Q;^WFx#ZikKXP)%%td_9du-TEm1 z(cpe|loT_>OV~Lv`yTRQOa5zAm6;mBvO%(Kn|ej(13@kw1MF^Mmq*sP)gl|;&JSw3!^SmBs1K4wV^QPyou%JXUXQL_d znX&a5s?8Hbms%@fbkuAU!mAOGY=fc^VyPOPAS#~TyLOfHg}f)`{MQhxn=NmM^Qv@$ zYF<%m^)0l9oc`b=cq+V5r~#pX*trSvn&F~}p$R&zP3l*v*8d=2VDtfkRptB#QBw>S zcY<~CK1rX_***ki^i>egqq^}DsPcLCTPO|RPrKa~h+3YB5J75tPb?``rH@Q}L ze1D&uJ#2n!f`VWI79_+opVZ^IQUa34Gh?is9j?us8?WlUf*TIk)dJ)|HQg^UWmvOO2<6BH=jTqGm`LCUj#?{^QXAs)Af<_J-MmD$J&pb z=BIzvtivc`u$AF(>)pv;YsI}h9>;FNS5R=guAov~xr!Fye?`~~R{f3f;(tPaej%5N z$|x$~zTLxpA^X#j$kQlu#-{Ps** zEB7jy}(u11chx;r%F)X1H*O-`E<+a4-Uh zb$juB#}Os8@3;iT^D$Nc?wk?0zVC~~+JC}>Px~6*eCv7m!LRrSCvJcr#F}HRF&;B* zGW6POe+gIP(Q*&p&#Hgq9{wuRMziFFcNN30qm501Ff3^ltOuNx`z3>Ms!V_)TLY@mK9@uIL&4ZlYtbOZ^j@Z6On zwy?xJ4_O+_e)nsls@Sn@93cN)o6$Sf9w$&rN>KI6LnxNmN-m}XWPrNCO8 z%toZQ%ko!d^dA59NLMxs41;{46SKa`=>}I<$jenr4Q$sA0t|GfcjW+XV?M6`h{U|$ zz2-gc?!9r)E(x*-Dih5| z_9oT0S(2h1U1tq_(cqVp~~<`p?teiQ7zb&e4R%##Ue`DPS#DQj?;Psohp!Y zcT87^U$H||0v*m`9nHHRCB7Z~d-R0Q&>Qi@Tj&O!!`>!)o*jNFqBp|JB2XA3%29qY zji@wEpLr{yg#L4Hzh#DK&mIf2e5-i~TGGqAe4vbyjA!H^;Ml7^(ZwS-sz8>=j7GS%(+J zUq=(1S@6+8g6afiTChB^s&_`ESGJ)w`ev+j+VfPo3J<~N)O!fMD&RIA)IHYwWUWYw zfHGK1`}S!8l!1n@cs$hm&l8BzY?ioj_VjnECQu?O8LBdD9i^^Bgc-YL`)$i10a}u> z3y)Ij(=iGeicx5 z&XA~KHCkbgd|O-uf-}wTuX?U8jZ_H|Fsj_kqMYVTuv3|!Z5?xK6-D{z1A(3AuIuNX zP5g-UOelGH2ojLuKqcZqU60q*YxN1GHCVWqde(|C=}tR#i!I?>m<0fNl4~m?azBMX zoS<3#P|5wBTpd<`&QiSa0638IgH9q8m3S#bSw(yS8?~VpxFf}^v+}|Xp{wyl5KXek zk%D?=2g}ZDM<8QktD5m$-s$75(i~HPyo4b)tyv+rD~p_Ecq~IiBU}b|3U-3DtmRjWps3n9EEj zhYCtVXz{Y*sbY~Tq8ux}RUW;71al^>$g(1v>~n49ZX#R+4bcyWk{WLH4y*Y*V0H@} zQPH$CtaVtP-&I}yk;z|#QMkI`rOa2W)7Eu+Ip1>YY+rOTC*doj;um|CBZ}js9S`;0 zrv^;7)o%SQ%Y{<5(5W(1&WHz|B6;L*E=uu1WqcwL8AAo>Arkl?3_r>)COX|x+J3MC z*#&XCSibf8!5Q*{2%OA0LUvej9xFzGnd|$`pnmLa(<8+Ec1|2T? z?{#(coN2QN*nJlhK-Y)Vd)N0R(ZZeY$?Bm$=^V+ePi*w{Rq?FrS^$tb{H**<7D~Ni zB}65qUdvt4F1`+Ffnztogl_LRWb&-Y!xy; za`?5wi`1J!d*zdc8$~Pb(jFWkv>Pe$@ZeVaYg01Ed#k5;NhvvvehqPU*YOC`__dWq ze`FLHpf7-f$-uSu@@i8szdt;A9K+!bo%9C3@TudfY(NH{CZ@WU)sKzVU|~y<)xnC& zrM&$4)uVDFTkpnb`O^>13_(9=*WEWiHdxXQE!WM(w`2bZKa1>e~ln4ff+nvIMgt~|)*;;9pk~Cr2`9WUvsg}hJd!5@f zO0IgT!qa+N%t7qFB#%L~!M2XZ=qu?rQ6+^h8UYU^A)&NK?jRveyiu2s9tw1Xsg@;o);NyJTR^2im^m?S|5csG;~upp>(;L+5bAYU1CtpuWKhCwg*%6=cXb!!zX13+<0Ozhr1f5SHXFJI zikb5lR-^NAN>dCv%T%Uw8!enS%Vtgh0&$C<<@EG;y{T#8{jcJQWQ=PC0k`n> zw4`+^JMWXya@G!2buC(l(usY!BOv8f-nF#;HA>U!?g4fix*tYMeiNJIy_zCL*sZQ* z_`Wli>}1JtiXluONcGW>Lv=z^(bpS43|*7WXOL0hNhk4TNgjP=y3QRZMNdJHW~gvS zKTWvk*zmyT54#6>K602*&~hW86gxLAlaJ$qd^OJzi8KSLVUjuW(~>j1_KPnf?m#W@ zuqoX(p0Ebzkdq9v-j`)Z_s5V~#L6!#!Mm3rSl?vOW=9V=H9jpj|Q!m zuZ+9=IYRQ+yLZ}Rb5#Hf_mv8duTMS!YsxhTi2$4DZ#|Gf(&7N6$&*bJ`O= zW3(?qWt{oZ+_c1(x~z7dCgK?2gm-Wgi{vE@Hsj0U&T*a8EyAcPL|h^7oA*1-wO%K+ zx><%(728EeSG7NTUG<=dLEHqgE)Of3vFRY!sk6C*ucB^9AZtIA5u71;W7gw-;j%09 zdo3RKmN_cy+FpD{jkIHA^BVK#Gn&5Kq3r09KVB@*?{@vNj-a~%Or?%FqlI6}CRTi& zCAuAFLk{USt^oet;JOJcVI4rBP4A}9`N}}j9aazN-YIJnnaYgR2vZ)<<0(wAcHRHo zj=}+URIp77(cjV&DM*FLadS{16)ew{@4e@zZ#rJ%WA!N!;YM7ISjbGgDd#RvArgR% z_YeVLoW0*kgCW_k+$)IAxP=`VL$DCbFdz=&k0esm_wDRdWRcbs-^23srPKG=+ zBUoy$iK;}HFvvd}NwmhBAz+6S_1j}<11xaZNQSOXGA1GOMiF&LYOn6kfSjYg3V$DVwo_XZN zAl&0*Q#>7~Qa{za40H>b77TzRxI(hevT|-~49{R_8#nseMRdlizBWt<2x%8et83NQ z&IiVlw*R?UTgCfwZO@y#?!)`ymPhqFrwmo5C$vXLBeh$dW*?s(@M*4j^b7p9^Q&l7 z8N;EJ6_7tHoIm#8I~do5Uf8VN&v(rz*b9|$V(__6(H6GM$ACNcZwPN#ef&x%mTFsO zv_)9i5O})LDY=kJ){e2d5-&W$Xkh^z1%PX|I*oYmPD{SQUs<&!v;d4mtDj%jH4_#p znv!MzQIp=CG+tpEv2()3NU!K`?di>qD4>i_vJh)b*jp(# zC1OpO;Zast3x;AMG~B6#|Czg{)09KQN}|N0zuXqu*C@sp=XQg!v^q46E13eX$%bXG7)(j)jW1{KkCB51?R9=I$)PyH-HbXw9x)d5YZ7LV{f= z(Q~zh!jgFn(EkedwE0ub-`=Bl9t2Dc`t)nS-}`kjM;4$Jwdv(=pnOH8@I0}x136fz z7?mai&E?iJ(_PegZdskOJeK2}tR1=aYVP?rth)g&E*s9Y>Oxk1IfiNhS=|;0*&6so zm#Nf2ras>i?`bJds zw`WvWyvLFLP}^R<3?yFP^*xEaX*N-5 zng5N?8UTM!A9{i^i0MP7f?{cio5NY5Mg@Ck0OM62M*jlUN9pI1(oLX~l5J{`s-`7$ zu)%ddI7#Nns_S?UU_Y{L^;-?)yjM4jRPF}WFapt4SXug9)}Q3>(?E5MG`Ix)60ZIu zIDkG6dkyvtLk-_o=shY@k*1U7Vi-)|tb)t?Jye5wT+;SMrt?*hr}Q#P$K_Yvoleqr zG~1|u-iq9Ie$u_mT&yes*aG8bq~hy;EQY^=Cf?u&cT>L6T$+Vk4VWC0lf~lQOOKJ_ zB;^ATZYtkQ`icG#Ey%|!;1^w?G1o4SjGR~=a!Ja!sPA9B#39vqLJ|(NqCA&gbD^PH z=)A(lOFELv{C}IS&>J8;#=rX|-0}PE=^r2QMZx;$EtD=V@1qo8hkR=Brc3N_F$afd z;0IPPJI+gkrz8QNV|e?@E9Xm(FXki$R&De$Ws&aEhRBfs=Fgm!VEUT?$UjC-%G>Nd z5GbqE-G@*9dUP@JRq%tWWCj1;B`Tb!V7fPM#b27O_YzW;C(d=MUw#d85Gfh3h#?Wv z+m}qf91}1C!DD`V>`RZ4lagaYQq_ol5$*CKh`{6J*X$h^6Td{Gm&t&1dmUjMm%mZ~ zmW9mKptOIl`i3-^)79Go+1S6m$J-Cde~?h#h2P}HpQ=d$1O=dvIm|9^$X&nzakHP( zm;XHiFLFctOL*`vui+fI2G3iImroON5GqXYRoO$S9o$PBLVp)%oe!xQkbl!!87VN9 zW)VLx>(7Va2M=+77dU@?|Ms3&7>TH_<)zsQ!2yF8j;A78|N66kHj$PVEJ7iH^EZhO zrmDmXMv$U}9NqkF^Zxjla|=*4k_~$Y<Mg9AGj9euQq#IcOuX_Qm# z-y&fs$?No1Qrp6sw9j78z0q-9jq*P^?$2Q&-guqj7+3Y#`OCa9{5KD*g13KHcUu#a zC_IGu$I3OsP4C_{!QVr3e=N^iiu#4%8{^FyQb@(OBKo6imq-*5 ziQF(s`k>qPj_2zC-f<<8tH*cwgZicbqdJNXP+r>8lmgTh|3}9{>TW4z$9?RV)^x#r z-_ZWv?{cQVv&;N!Nc%#tb?j#6k86ojo>7+=`eKTaVxK&2^!>2o7B^`R7gx@Hw_N_@ha66nJq~BWQvXJh|C0K@ zr~2a5{(m~vy{Pj&@y7GLiOX6#*ZV-^_RAJvIvkfd}4b85m15mRCzeOo9DIwi{3PWH$gmz$Y*&0T!y%pz6?Xx`SAbu^;6_x>Wg2jy zN3zqz^#hClj7yo%UNA!qq z$l+FWZtjr%FNXUrl%j+jj`vg6Wefd34_ApE(R{Hw-D^7(|C`|?QYtTod!6;$_3NL+ z|9{`iJ@ldWi`BgoG)7MBk^!@ebgZOL%CIY?)l3L4O-ABLiW-u3ib7fRmp4;%Cr#)t zEP($=M+Y4Gi=mf@U3)JZ!v7aKeh|{Q7#bz((dD84<8Zq;5_6G(+-AGMe8wpMid3eiuQ82^jm9B^N4fcR5Uh{`IdLVvb%Fe`z_{w zIkGqiyG`O0$b=5cxDgooEN-# zaECDNvbd8H`m`^ltO&4vxq%XT@Fbt}a@692-MwV~*wj6fkC&w$Co#&FR3x%RFxGqm zkNm&qt9>!@>gAECs8O>#51)#;NO42@U!l957h5-k>#nUcacZF@@Sw5_zbKp!;rOOl ziw+`u&%T4wM%7Z~+`pVeFKTZP&07zpeWuEMLF&vpLKF1A8Q~`gT+GXr{CQGypuok{ zpw0l!CO`NTNO_MJ?SiwFg@C(wN_8(I%UBHS?|Fv-#~q4`d;L$v*$bSJ?b=Pu;zMF{ZMP0Y)VytfGOO#|N2|2gt2;L#Bmv4dSKKd*yA=4N9 zTV(A$czW+1#-EZY0sMqFJ&g*L9mpnV*;Db33w=uU6zK`yCKNVvF&aXilt1Pf0G{5# zmH$(yeFxzT_+5hQkT7+ym{@XN-aq0RcY(tn=TQP)z>$9Aub<*b0Dm>^?c2W=VGMrK zn?sN0-3=Dgv(i6cbFr^qaKQR^-uQ;aCEbPoZIKAw15ZUn@c*32BM^dc5EFfZ_A&}A z#>hW3^ukdT@&nY&qGSdy=+60vG4a3>>F%Zdxrk4(SA_F87%_yY%D`ec`0VZfP(l~s z(bE@@|C0E>B>wLy2GZfbr}#qJ{8v!_|0}5TirV`G&n=946HEn8cN955ZeY5@=`rZv z(gXS<8%1f^N|v?aJ&OVO0K=Tt)05@kdXdw7xcg+c*t3wAFhOn?=Kz#3*p2K0{LBV( z7to1s9%%exb&#FXV6HK;2TFVvwFhe}qcyE+macw^1+2XbqQ4c>{$AJ@N>GRZDA~fa z&p(y^=Pd&*Qe{!lA(f!AyaBisod=D>pvCoDfrkJrL@_zsCkI?g#)gvf#eohCabh7P znO1KwaOj~|wLvNL$w$~~#Yk|`W_cFndu(#vm>#Z)?s7AD=Ltx4mz+RF1kr4Of{Qqi zC?i8I;EYI(Jb66ZcKXY8b|Ip`kDFTmwU9P;bK%_exE=FMGq)->b{@s z_908-^GDDp@8hqL;^Z;sxe&}K17lMF=DgWqbg(fS5V8xhEp}EXxJ${dQ^}zNZONpE zzj*CJp(|kmg=}_LviBOwCir4*tmMzIq8uY&mtT@dODXCr35M|zGVkmrrCa|AxUezVW)m85<|zP1r4h*H*l$*kPIn(a@dWSELWd*o zM@YmY?HI3LJvEX&TD-<#K1^h^mcRqerJWzjh&bpe7_ZMXt#M&014*lKMofj$BmoMa zvaryUaKSVgNu-}&2f+j!1%po|e|q+E!pLdrT}-ov1f*^I*8p@L{?gpZL{<7B(5%>b zIu{{8xXNvT(&_C2l4ghNeG-)&ah|a@l;7pG&rvrotmyP8@N>E1Gvvp+1$UoD%Y{E~ zOwe{gv2NImgYBfc_JqF^v}laocKCb)G?=t0GxhK3))P;9!L>eA@j~5oAvPNu8ZLPD zu*&q9+>z~#KUuh@@l_O@FDzinE1~3%{k@ogU8kxLslEmd12?kxJjE`Er8Wn^?zjdE zm>U3`ty~@e2X;-yqU7@pqt>^$^8io?8Vyd2)eK19)hlW|TAJj=v_eJR>AuU>)e!Ci zSrKc+^8%o(?gbLQaMA_zQ0W1E0!iFlm>ZJGI8Ee2yA+;1Ff3}=(RPBQz2Yqe zZvW#vXpr_?mr}@#=lRqzE;1o7anBX>jAKLMuOK5oP%@NS9(m$X{jC(jXOi5fAmi9) z%p^S|(DJp*bzqqhdbJe&W$bO@-8oq5>g$0SCt4)ImIl%^tY%8XdA4H0rL&7P?+?$I zkC6AoTi3@ijaYy3n6IBrw_9cgr(q0fT2GaxFW|fapO&QZ*t=TPcHpu+zPRxyuUFPK zfHvwOh`$MxeeD#YfK=R)GyOOXY}DanoPKW%s#goE+#_#u?#JnR9A*wTjHRCo*?p#s zCbGzm^BJ0Qu`+KgM0Z`8fIJO_Ub{iDSiFVqeM20akk42=_b0C)Jpj*s1Qglt_2%=J zApekERm(>7h30tVMPvB=X;*JEP|cP83!WeSL;~vtgUfY@1EiJ*`b!*%6zEH87G+gE zdtvae`>$7bpu(xFdg^^x}VOG1Qb%UYUyP~GM2N-%(1Ev70tm_sBuf`#& zeR{#ed%K2(npIptyx6|Et*&5p25x}qq+I`&XhZkCOGmWzSjZeTEQmLA`u3cD5_vy(iiY$Cq5?>O1L@g= z%&LeGXAfbpc+)5W!mn&*24^RS(}D=~2kn)k`a$(%H`>adlB2!#vU>R(6gv$)#E(+t zL62D80g=+(qF&}5f(fU-3KNkx1hm4>LSo4xBN!N@!B`YXBsCQT`I zjON8xCKZT08`@CRC!mCat%S8?YIy9mnJ7Qs3u4C0*-Jh@xk!gJ`q3I7HWSU6W){z7 z*)4frw22Ix%!j}j1!HW5y^_?bI@J{9FWS`spVu9@kA<>FWOV}Q(($}scg9SawgHsf zyu(}x^}B6;sq?Mqrk|^r*3Rb*RXyKN_c>?WNxOS80zBu|Jcj6<%tk>1uZWtz&@NKR zCYAn_rosWI2^uP0S(*H*8Qo}QwAB0gI%xeW_S%GvkYIQp)QUd@0q@wlQp>*FFGdfN0lQpVC61CUX)8A>qG=p#zc6By^-s5(eS)2e8$Se+OK94-Gffo{$ z{z9UPU1%gJvz~S>ImE<@pVe1-V^cr9GZ~3ze$>As{+ew~h6p z@o940%<^LwYij}L^TaEGqJ*X|dr8l3mrW!cqgLK8ls-FnTDDKY7fQ50W9Auz?P$tXq3%UUi|L8mF^34(7?(=_rEJv@807rM1 zu4AH3MZ=WVTDYEWL@$#XY)#bK+sFw8HeoYF64v84LIQv(-RLAWaTLPQ4DHVc#3?Po zZAbLzF@cKO=LDx|*zUaeQ==SwM%`Xv0;$F%!NQ3*a zT`;yAgwF8Tp#2ty+rcZ=o#H8#$t}RO=Exe|>M09k6y*huLJbOx z?S5EppIGBgthFKs5@Ktqi#5jk)ODV~bL)^>I=^BV-9lUf>-&rAD9XsxnLNMnaBm?R zFl7nm*ohn^Jr^Qk59^$;5>3kr<-F|xjAd6)uC(BLBm<}Sr3U-W()v2AHthN{ILJ4H zo>n7`43$kNzOax^O0A6~JNgqmpKs&H-UH7C4qs-QJMlqo@&+t#nR0FBlWg62(Nu_S!g!_PI{~8| zIQK-n0_*;0YIrRBuXLe?vR+cXy)o99WClqI_-SdqR@2dv-L|C+@uR{jt%gW-&z2DB zaKmWK@?=NDCp|A)#&kWEx2>aMA0IPqFS9nqwRf?YM-`enwPp2m^Db9P!e1`z74~r3 zHTJ@JW+Uvnze75H4y$_!Jw@go^@}BJTfPNGR=)923W{Rk>z4WYm+r%kbFa6LhcrI zU~!FpVg^!qx>DhEA6P|qVeUa=udibUlNIeuX&8h+LAZ20R}N_e-n;=Kb7g_WsqtQO zp?tl$$x_%w^*L=Zp{WM6W1#=6Wl{ZBXNJCVsji@NXC05}H=15&f(b|OhkBIVW36_kJ$()?oD*l&Q_n$4}`r>wqSmM!A_$QtrH_bZGcq$!GUUd9 zwesc=D58cwq!qT@v_%l~#_Hv;uBUtY!^z#dwPH0N2-vl_?MSG#6_LPk^`t!7f=kI~ zg|$n0yW@Qi7dC1S7&H0-UQhjc&gilhey!FF=2XV`IX+iT%rb+U)-Wjr;B!Cjd92gm z`|dm5zbXvQ+!5E_HGD^pteCay8fBZGEPH+U)pcijD!BfKpHP{N@KXKyxnU8RdjM=@ z_W0V54DFgWv;262P{E#^rDXwmshTi4f&GY@nk-M&ri1z^t?4bsd@k-Or!~u?)rw?w zyH$tny=40pz4_$g#+V6~C&Rs^8RYp~*6$~ZUwysJe&_7|E@yC98L;>$2`YixelQR^L9)CBXLX)~a{ zyvcHJ3mgFOM}<*<_lkeqt3Vb}tW0KaN$?T$a_OH-*}P>Z$EW`@C|ZhjKB&|qK7<6y z?jrazep=n^mK(^R=dxfLa;lqLE^e~gVNo1;Jv|{d_Td?eDN?<1fc~nj%ksOR^Ye4o zkl2}F;Q6hH#ta=DY|b_Cw}UFV$|k;z-1V@nx+J?^9+?y!pX6%>;V7|rx zikCE-Qn1eI+ef1Z$jUJ>K|ee@ZJ8gc0IRQ_RC@|FlkM?Jf^8>*80#I(SmA|n&m4#F zO#n0HI+@0wL%VYZuvItWlz5JBWcJ(aJOy$rpNJ8!P8Oy{n@1bl-UnxzuzJQS;_&7W z9N1V;meKmvZnC&Io@nX-nJq2ahs}3>vK?|JhR;5wb;x}}5)FD%v>)I9JkbUcWo_2S zhp{r2u||G=4e!Z})`=TE0r10CwNC3vYAku5fC2P^=F2kV>Fwu5bek@qzJ(pB|M?TO zDUY-!vbA=R7RYodqB{*;3+m!?7xuNrJJF^_`kF793cwyVUZbE)kb~O<6=ki8#iN>u zqvuXJu9!X-uzGvwxOU#Rgst>e?Y$7+O7nZ8SVN4N+cL>qV`RK0tlMMR$s*-=O_HlR zo?VZP!uR5@V^}t{`OW9E3f{JOrV)EEE-r@h;D)y!GF$)&W?vZXM;?$7g8mvi<8)*9 z37V1`Ui)B*$K!WwSB|6(?N>@W#^bxSou&-20YL00E*^Z5*rW7)^2Qg0%5-M&sBmQ2 zw&S?vdY=do4#p;t?_Fl#zv$`3`YXS<0SNlk3+g|9zZ!un}xoGGe7UxH0NQ%6Z@q4tCVE2?dOoiCY#O8HV<2@f3hp0PRzJ*z@z% zvDQy}D(jIF@vP35k`nHSNY#2L{Yq!cWaDoig-1Heh8N^0h4Y$!Ej2iQpq87gG5L57 zP(bE<(?S{V5GsHGl6i5=a-Ca~3@n>*JawAL0FF!-K2`hE^rA_VthR@u!{TsDKun`f zg+aZh05f+BJEC7nGyF@z*h`kzibJLNBq}xc0}1TX$A^L$V3W1Z_*u9L@tmgmI|ggE z=`S#IkvR$+lCH&;{SrST_W?%p>|jtO#49rKj5ENMDI9^G?dJ3mZx z6E25iEREsGpp%kU20hqgCIo*0c@r?y+AzPJqamH>9qd$mzC?>rD-)KG5uCE9}FI%ld-ngHA#EBCb!$k2KAJ@>0|0?Uj8RpxH4uEG8ZMn2 zSjd_6o1?UcIP!vD91}T(^L}jHhaDeTP6+2&r6Eic+G)nk{GyuMTYXt12X)vC39wZm z+RZftfu>AFp+=Dm9dCk+Yl6b^)RQsZIFum^U|m{A2nj-!td;UKixyKP>lzm&m)#wYw5J# z7*{qC)T|go>c&%C^8?0&^9M(u!@@PzmC9e`?_-a9noEwJ%08MRa|Vim3lxWx3JK(W zdagnxXNa`@ErfW;(L{i+7~=I|Q%0(18#Elf(JP!AAEA*KChEJZd@xAgflCBYUN@p4 z9-|wOpLIzW#(Gw*u&5+TSSACbR=#iH0Lfqsj3yqXD^)lBFkWeCZoL=fdGzZ(tCZR! zk_0+mao?#2nc{iV@{ARc`CkXCoz|zld0qTziU&#tzxopCn1}@vQ9a#~5g(aSe{Y`U-$#PrDkq`ipSOc$M^xO!wLDT|`1S{)mJ; zW$S}(=MWFxj`Z-_olB==3Q$LUWX8-zytvml#L@eRdi^HHnp#{WEpA0Z*0NApC`N#N z5#SXCtA<^bg|%_vJRdg1XZpR*Qq5IHcE8$T0IIuiGgN)DhO&)tNLp;FAAe$jvja|p z<{K-?P6UDGhUo8E14AxplvK59NZ8Es>_+=8n^_&@4?Z)>2`aC!PH2A^nsn^R`(FbO z4umb{WccFhD4c7bv$dk=Y5zkVzsxL z8F%AYSf0$f^PH#S7KN>}RpNCtPEH_Huq<17X8a^lSdtP+?~NSs8wZ4TE||opCDUf^ z4et7%nuF(yRWy)?a! zVI_mTrQdnzc$lM#qj>S#(*IF0uxo%)-)R9bPFV>L9XYF zdPe}53GHoD-VCVwS|`Es*P*!wY#~f@Ag(m1o_em;L#UuE4pY{?lSahrca%!$sXcOZe?D`MaR@8a z2xFYv)b~g!(7m&QX?;joMOQe_sF1-zYI3DQ0VB5-ceuN@_9GI`>mgHVQD~P9BeHY_ z@ecPfGdaL0J^Cvq| zsrBxx@dRT&dA;{b;W1|2{&;Wwey?y};HuMTo0faa>eeIsBn?CMD2_NFmQ(VPMk z97Pm#Wwl5TtE>co5WOp)E`2Yn-Y^o~^_YP@A?QdxeDI}xKkjjXRb~re~;#4V1+ms_VouF|3 zewB48xgi9x0?dCIK}Y95Ld9Ftq&&76MB)t*U%s&Iiece%BVhF_{2 zJ}M6UOA_U({>qXtUh8T8^!OPNC~1_d7&_)Gljk_Jmofk7Wkp|J$mIxNc1U_uo*D4! z`bNpe7$aQzE$TT(LXVhJIjs_gqL1(t94vgvMhfcanIgmT)_odDk3y^C_&=+Hvg~wO zZOu6H7`m5Ljmt-_nnXIiMGGP|oj{Ce+?^9*UGn=hC@Y}vM^^6i{l&GLSLEcJ$UO{ZE;xAv! zovY9|Cuwi5byS)cHw@niTd_R*7iLq?ID zqNfayB+Mzl-I@ET9rMzK$nJO*^Zu#whp@bWUwVzaebtMuQcr0i16-L73TKlvd?y<% z4g4h@HAgrKyA+k8L4@8;DlXC4Z{JA%$eOX%#S$I(@#1dlp$R~$fakMvj}maH=q!uNP#&*-;g zM*O#EzO)RUZSL;-3(`JJ0v_~i&$Np+dMTHJ|p2bRTKo4Y?vO5Ei3i z9Z~nrn`S}i_ILBB;wn*;*5cwDOx#iDX2F=aC#w|_9b{VX5Kd;?hL*dO+&G74vRjg} zJy`JAR{_0`B$%(Ll1VA3nNM6j*JlWTxDfYXJFrc0u9tRD=m_9XD5^kocf$5TODC6w zrEgJIVgxKV-*^GaBCnAu?l|i;bIgBof5QrOTpJq+u3gIFeY{%|R@==U(lbFR-p6^{ zX$3))%zb;e4XV=%D;{niK|U(+J;|BdNs%c0;M}iBZ~p0O1OB%jwo}T{TqQj@k`<8eU2p z%F?j&4R)-}9ib`Vuqs}iFg>wYAIS=@lEPJhLQVmc({>%g-Ku;*Ea^+q-l=WM`?mxQ zns;vUC0Mzm<;o`&0kieXc2wvi-%nZeS2bqdgU~fZl|@RT0f50bIN8+*UnksYoN;_P zP^Z?=(UJEC$pkhcNG4E({)FYw3~X63RY|&y16=-Ymbw)@(ngL+e=~4p@Raubz}aVv zr3mG^2bNMQ$4(8Pn|ADUh9ZRyo>mJFsx|}6UK3bLsQ$J@y#y_l!LTj+-CcaINc{=Zs?>^O&Tr zvon4K!>pt zkWR`sMNa~DNh9Vlh2XpWxz5Qxj7WH4D4aTr<--AJ&F-#9!N)Ti$#)Dgyj@v=q>k(X zSVeM=GTZ|_uQA!g-neDHIBn_cmc>w(yv*O~MR9|=s;4HO$wLYi@R~Th>%><weR9Z=eI&-{%~3 zgX+3lu2<*tte$V^ROGsBp%E{*+$ zd5k;LbLo(BDzM9>af=3>-&}(HNOS|VC38C^RM^r!90U;rfOyauMzN*5S)(dz`Hc{b zBNU0z)>SbMX1@jK62 z@|=l2@X_<$9}P>vtOtHArhX&H&iyDXb3s|_fx>7OFYibxT$)R}0w%n0N0tan6 zT~pV0_qaTcEc);9mF?eRJxRC<<(rFt`61y=>>uIxJ5RrPGw9H0&6}hIFv`#;m<#+^ zDJk9RHq+^cY0)p3YNghR>&v4qNKRS@#6PVF$z7GWSkM$j2>v>@=pqtGe7?6U80V>OXjX9*0153cD z5VQ176nchj;>(gJ9@Skm3qW0<%XY{UwTbh|(e6(zIju~{o{G>8H`Cis2256uV!CD3 zibOD{f&wj(Eg&>cV zJ{k?7UMFzIo!EcRcsxQU$=kx< zg)cNY??CiqbCk6j0WhbFOu8~=h0P%||AdMD35=As*rBd9M}He}nGFLx+X|G3Z%NxjFdsJcAwsUnRV`>iPk;#30EMSD@&_APl%Qn=*sD^pw*Z zxtogM{(UX2KFFwIS5T`t4TuBWjx9$Ad^KB~Zi0gK_sydL5&5nz!I~CHZORQV1S+>c z%aOHTQEzW$JGlY_!TT(>9YmG(7g6K*&}b5^H$h+xnx~inQhrePFjwJK%>G!hVS`+l zE-{n=qR6IV4RM+S>z=+_VRa1v{)>TtPQMuJBV5Od6e)8UcaH}VX6FI;9RyEeW#r%i zpxTSovqFfIRT-+|pw50__Spnoh6Q@wN1jSB+E{9ml_Eq!C?Ej6F&%b1H;-mv!EFO! zB}TI+sVi5F*~FO$dOTa{%<4CdS;E@C1vZqnB^{2HfG*$9vXY{Rd|?7d+M-!Zn1(1= zySAeo^b0FD`N#_{-f+H}M4Zf!okP#t3v_b=_36Xhl4B%w>!Oc-87T{6>aVgokE$Zem*A(H`X5hELp~=fe87Q3+2RE6zX$Bc%Z?@tN0a`SKDo4a z{WEbHVGE-!Sy%Pd@_BLbJ4v*-n@#Dq3hE4R{kd}jqfI)qOMiv~MBJjCH3Y=-9(+KP zE669s+Q?5(f@mJ^fd(GO`8hLr;Z6J&I=c<97`R1)`{C;1u!2DxdUl&OeS)1|jQv;* z;8l&TGjess)bNDH70#!I$i9uqH6^9da%XQ_G`RX5x2aJvL!YymsGVmR*O*}$J`0?^ z@S^TKQ?HWJK@S_D2Ool}IQ?_`c-VY#K}4eVy4J2n93Bm1FcWos3ZMWq+UvjRhm|@j z`SRp#%c)Qt!BPDFS=ywBIpfvIzB)n>_Aois+*j;yNhrrXB-VGwQU_H03eMk3HSUe} zHCe=Lw4C1f+O9l;8hb1XIJeGu-Rm6bzKc#}=TGTVuWQ=HaCmY~_AfpA$`5=Tgkd)*_ zj>mot!J=SISGTM*P~MB<1Pn)UN5_F`z~>}fR>PFcpW8Uu8s{AFfnCgk_>VFjrEOQg z!N@t4Zm5D--d^_uH=pAFbMtxfAoZ5;uZy=DPbQ%+4FczZ4^Nb*PvKZg^}zIh9;OdBfe_(~^^`lwisoT|&sH4&|EpfYhudUK%qFztp4{ zW&gp=s^abxt;8jm*(TnAg&;Kxtttq8{H%?jB)U6JQBJEtoeR=_E$?Zxcu^=tjSVg< z0L=BVQDc~@hI6*!ea5dSr>ggEKKCZ98JXHER24cjH`TlH4CgB2jCfit>6?d=_Nz{J zvu7EJ9?)`Klt0*!O04K_8QB(bSo~hos$wl&r+GTX{+JylT=M~52%a49#ff(UXYroU z+*Ouo-fw##63!$&hhPO~#yWKDOfC(X23tUTlLefeC9w}U<=isN zU~IY69;ZYApr??D(CW%dqn;rpT-aiG0?0*Mj-=W`s*Z2CyV@xGkMXB;_Ec?gwT^Z7 zeyrq0|A?7V7|*eFdV-V@a!SI3IzM?C2tEYqT?P{ZNh_4E{>}Q7`4Fb%Nj|TOqt;L< zQr-(+O%C;0n~B&r05^J0XkPU=NzA7?ZtFRs_#l%-HQ(MNLjC3H1vh3VB*GT>*v!qT zoso`myP0?Fm*Z7l_tH+zQUqO|2ar3-6(>#&g?dLZj~oxAOGO7u@9Nk+SXokf4&yAd zkGVc5mb5a!FltHJ&+TUIl#7Cc15L#YwT`lUlw}tS_-{y7Uj5geXF$$E7`l>RmE?U( z<+F=jVRiWE#C&aqRo2kv<{9moQ@UjZy!V&=>O28lUrxlWMFgczome+3(*5wWs`H}f zgOrhDGns_NHO>z~+2t4rSYR10iUhcM5|o7!Uz1UbTW&luMnH`%Y6o<{4F1;9z1_qdNmRhKL#gm@hstQiz4 zkz&}&phR%Wj=3E@F&cHR&mw{*7Af;ien(Qf>#6|;^Kl@6=hl!&K$kQ62K2@qL(KtCo*NE=kw9!!Xz z{`HJr8e+@tJBb;}IFEuW&4q&Uu$5&>GeL@Z;dlP!uc%D+KCjYB*nC_{s}$bu#o=(9n!@9BM2&ufTM^STarn9-qbbID>5Qj zbtr_h-PUDyW)hHv`5PzQ4afTc!&AMq(XMs*nv55eA_8KB2lSz%H4*u0-#zXyzlJp* z!ZyMAbO6ARJ(m2qT>J zaf((819V|LohgJLCUbUx`)zn{(BE_85sR}jsb^hNj2{%u1Yyrhz+m7=>q}nLITpo- z-s6AiD~_}Tv_GW;C=7I8G4qkDL{;8F+dX8q=b9S8%H8lt3=NJ7v?wQv&+oV5K9chQ zRoX}Eei@d4E+#rp_~sWw6}+1X6Two^@kl=&p^S~#yrh+?(V-H^;I zgF)NsNydnA&B*h%y*?*c5e#fsotgnVh{qoNmPW5|&SJqH2RsnsCz)!cSeQw#AK5l$d7zHm75u+b2q9Q!4TGC*Y#m%7jx=OgyT}D2rPw=}iZD z__6Xv-;v!cFN`6!voIuR7F1pYPrzE;hE;(}>a6>CWe}uYpe&UJM{M2+qLn~_I8b_@ znb<#4jfyuL*X;4}td2c_|Dx04`n3e{zw~0E&#%QmI7-6jy@zt$Cb%Lpx=TviuzdXv zkql_8(s6a2bBfM#C{zqD2Lq z!6unT-_Ow2z>q%PaRY(pC8akw;g7viCQUk1-7hnEl|I~9a;|Lx?j6pyk!#a&?{&D! z(@;`&(urcY(*p!VO6+F;dd86$3>qBDgv#dkvG;L1?)sVGJi`@pa5akbr1I{ow5_q6 zdvCiw(_B^7@AXr?Gbx>k?TaQvHz*V@&lH%7mT+ydUI*S8$hUd{XU>*ep1+&WRtZI= zuI-u@K=>uiwg)&`76$WxS{-v5Mi)Awm1iv51$&|~2sK`C zNJk#B8_4XmfaT3H?nb|rmda3dy!LD!cxH9$yHt&8KTyik$*OP&)NjCMzXeTNb6}vn zR9;8wui}6-tb|Nj^(=suRsjpB-*)Jx&Ux)W^nAX40ddn33Ygkn$SIgE5iZePai#!I zG_RSj8uXQ2zxtul#K;*;iZXzVC?MtiRF~rUn>`Jmj&H7ErKepR8Mf2aCybo-#f&lE z6HtGH!5q~0tmroUF(uv1Z7J7As)ffKdg&n#CXo!~IiJb&oa0pO@yufUX*{ScDO&*O z)B6X}m(XP~a&>EB!mKcugu?~z!ahbs=#WL9#tF{>`l(Q^Ik!*_vN2h{0)ZO7fIpEK z*h>a|-lPlwll&-&+SEu5=cp*rtF&0`MLi}PFI4MgEKm)BOd7y68ARgv-jELW`bKs2 zCsqwy(iJz`i38}0v}U>qP7wA#S^$;}eww|T74`~PS|d_9K4yiJj*y0EB|eXOnUH6* z{F|Q9ue-VMaY8Z$M_ID!Ri3K_vWR4v;N?94C+#AUV5UiX*FBsicjL>3#zB29F;M`8 zdooGso^Q^i!UN~C2{dr7=v-Hg!@Vgr^ERLb3*f0|&hwf5NiO;&X*H^3Ahk&K9#VI| zo-)6r=v^_h7rWOENbfW zSoPQ6<6$i+x3VL9fM+2G5>c`t3+LivX>rPrNIJ*T;xD3I(B8!N(jrt_u9C!`qj!+M z(xB%j22H^g2~uxdfEoXCGw~<#;ALaktcBcz%RJdWM+9vhJg%B&&r61qOfr^f>``C2 z%G`)n6`UgIWW#z0)U_DdvaFICjRBETihUK?QQ``|A!b1BHdEj8TcUMUPI%t&)4jbK zG|dz|iBi9oU_L&b_h}itoG?}1yAr)|Tf8otij*sMl~gC+ZN9V%moj)Q-DDODiFw5K z^3{>b7;n??*keAsnF+w6c~1n@qG9=7RecJU19+m>k3bbIdER#G7yXG1q^Qme6J$BK zrjoRZC*0!o&D)xrGY)$L8$a1uO*%|FzV?D&1V!i}=rmrpEK}st1G*2DBc^y24?{V} zyF;^@78m7h&yJOvUTHaO&o@)YnXwEXu}Qg~9GHLSFxIEhvSlujqQOPIbUP`xdrcV3 z-F4)UkwiOh(5YhrrFV;Mf9Fh+O0&X}wYjQUaWs?|a#}pK4JJbPk~nQI7Y}OYy&K%Y za*_5Vx^X1kigVomef=tDD1iVSF5c8S%NP4o%~y3ZlA9F2?Vi6)P8K7vnq0ZDd%savU^(dIa;VW85~gad z#KV=NjERx%8+K!#4eAy1TuLwHwPOA8t7%5H*ZX&`dk*_io2WEjbtMwBf;OMuY5BTu zr_oc^B64;)bW$@w??w{mu2cP1;|_Fli!pes=;OX0SJ7V6Y( z{#<52vxEN?{lU`zcx>EGV(Riz||+Lrsst@ZQh6}>SZ3;eFm z>oAbM`o;OvsehcIaF2o4dA53o^E6}g2@oh9Jvz;(yZ`dzUugkzi_&$g9Ly*5O>OF; z`>pp>_B<3wkX~vyP9?uDz&Usd4i}V5buR{CF`r$TP%Urz$sYiyK`;QjvHa(yx`0xa zyUzxd#^O67lKz?}fCYnCP6qK!?5rbMjBmz4icU8EN%ZGH$^C9{_W8H$XfUrG8S}@XurjcFpV^4U08uzo zD(6m3OSFCWgQV*`Q-LPyWM_JOJ%Fc(Ij4zIDar;p^5N#Q`Gdx4RjW3o9Y?6pMnJKx zbI79(He%B5p8(oNFDL{{l`|Spt4tgHcKo?7Pr%4r6<-GjD^&@5YMp;P_IQYhxJ4(x zpDh@b;_;>uTk1hS!?Oa??g<MPr}9%_U0ESM`WB>}#R z(>>!nj7!qTAZfbKyO3;0e;1ZT<=lbcd>#MG+I*cjH3Bjz-uojv)@UkOVB54}i?U0d z9R#cV@PqQ%qE22O&c){NEvLDYuq4-KnZ?b6$xc`J=4}}RC0cUpv=zL6kmb)x z3*vmKQ7^a`1@7+zY82?H%jElY!9-o6#;F4S?)b`Vl*Zd9ntxE2XYk=+a+Jvs;ke?u zTW21SEbcS`igIer>u2MM$x?(-d@O#2Q)8|&shPl=25J*18--e_&c%4V-PDhX$oIl( zDkAS#OwRXi+RwdU4%blr5f@Lo@->2oMr$qa6d_o`O}^~NG6vDegpNpKA`Zysz)0ae z0bSc4RYcIy*i<;ZU@o?E{;8hRoxxLvzQ3rBA$aa#V|3)qp4K~TOg4y~fs$Acgxs1= zklCb0_`txskr3Sz5X`XODR-)3ReZ(j=D|}*q_Fs@r(W(Bhew@_2aPlKc6SEDuRrEq z=+1Vtvw+=RBrsp)=hM^bZ(7cl9?;$8=<+yPk4#dJ)vP8&Uh#SlvQ2|v{O-1gFfShxeDA`F+;%OU z^lbTT)mF$;=GwtQg``ez4~MUu;8Hq$K@O@oZNVNHxjTNEv=`qKkJOn<)gyp3>%L0T zUiIdho_W_kYJOE@W^Mjm*7N?87M%uV!T5W)0z>b%=Gv{L>Y%4PY})BX=Cx33{{P#Zz)N@>Jcfks;*s?2o8_vbz>8JinTi(n@r@mBhJ9DITr9V@$b zb0Gb5OWi^)BXzR-YC@R`lLr-zuL{8*FHU*sKZoopa--hv;tGD+edEYckP^sQOl^*< zS@2JWAJ0?v@MXy(b!1JG_NWh#GYLbE%wKk9>YVS{T1!Xbd2wp{zRi}xa-0gcY1KJMB~2?q!qP#fzg2^bU6PK+*d zo8BsGA4y5ag1HUgv;Q^23p1?Oac2N~Ajy>uGH%%`7bT0hH6zyHuxnU41pDTP9UJaV zXG&TroDGCSqw26eUz9b?;JJ8 zeDRQ>C`osV)j4TmLqZZdvGJGR+W?dpzs?LcPhGCMH^l18X&GU`c;554qjd|FK8_wJ z(v4Ji=V}9L`a>_LXvzT#$Ck@#B+p^6(WO^d(`{a|qO%PSq@@}5WW*Xp8jC+*V?7|? zQ7qSkX$JL3`9JJxAi=2^tnUT|vAKE!Z4I+cKe82F*FTrn^0s67vUK`YT|Kf%Hty!c zn@#i8GXc=Ae>EPCZSio@t;XBmo;t0Di+Puvi`Z&9&mX#0cqn7c^z!c6jJ>QlXwT-% zOL)v@yGfs!Tv}o7W#WCi<63cr#X7dEWM$54*OFj4^m*uGwHdoB ziKN+k>JuZ}1Z7^Y>eogao@44<#PH{wq9HW{m_ie*;o^s&2=4EdU-v%^%h$3jZctq7MCX2wYo zIZ1g@5R?Wc#;W+tR6c1YTV8SFbpNqTvct-hET4E&StxuTt0i&_r1A^BwL>mzrVu=$ zRmq1L-@n)La+|4hO#zh(5*_-|*)&XZ`Lx}G$awCyUav(RHGkOPWH zcn=t8cpdX%dQ{MLmmGs>VO%5rZq4%-)1+t3)OyFE33C(<*jq>0WF$Pwk& zMMz@?uoweIRcFNMD(G5dOTX2K4+Y53;QG{{TW7+-Q8VDc80AHCV{ZL+8mwP!)$$=E zmD%xQBAM_Sjo+Gb>|!ePfG8pv;tfGFPCO3~SUa>T965XW-!-Ry3mK@USFTK^95tym zfmxN&qg{%J=p=UG$NtDM_r*|wxBq0t{-1pbzLlumf2+kEIko>t{6!#@-aX9!M!yfC z$QeO`sf?uwCKn^xY||XY*8^ff4}-}#X(ZBV8I=>4*)%UYqXKgc@{mKgEzG9~gQx== zmnz_r#|b<~Wntgqx!#5mf08o6jd>~>3Wv&#Iv%6Tr8a??)CYm4SiW?TDr<iJyWQc`CS%e)8lK4c5io9Jc9nA|TA(X8+GXVM4b8kehS`0MqDsQV<7gJo$FtnG zmUSHbMvnOhumSb6dC3f$kw25#*GNnB`6BRdn^c|<(h@rp=_FcVA5-BmoALr!JB_}M zENRM|CpwlAo%GG-M>m2L8K4}p(Jk+zh+o8E1{`&>igVO4=VwjqzQ8b zO!M~RFSd#=OjGiWmvxNxu?Iw1f1EUh4l+jW$s~7a>~2@|^A@MujfQ;(G?wldRtYa? zqu03g3(%t8kkK{7KnU8A;;!a-$2v`et$w>J`hnJmV14ydBp+pF_va2Nl{??%?@R*< zETU~I$k9!B)8QT0jP<#*b5zug57g2g_z@X$(MFoV7;^Y8UPQ1^qkQ_LDWT?}A&3FZ zVtGO?Gee*geg~YbGUn}*kkv;qJcTCp-ZUWxwo$;VKQNU+0aJ7T0uA9ZXPI7i9499j7zxvIo|4ADi-k~sH!;J4 z8F2?QLVN;KO%7YgbnYW63N2o@Lkl7Fo*)Vi?Og~p@=G7}N!C2IY)d*Gbi329$TQPA4PW%Rg|7q%UF<;5)%Z710lgJVx(`ch0 zW*VZlXMU0xUE$@epYbVkZ?MLKonK5W$FuAQ7OSOynxB@gj|lF)1=YuMz$a}XmmCes z&BwnXUL4ew6Ic8wnIDU>MK6wm{KDh+?WDRPn?;}J49)O;?4=`@$c>;^jO}@Q*X<+D zVB8Vbt}puQDzvm?d@s5{=vK2*lEX0?|g$Huk_Yb%f_I#gtY)I@= z=NNI5y?FI)s*$d&b}s<^<18g??z)zD>B}<@e791@%)Q${EY$aNr>&&>Uc>8$+$Wh{ zKk5EE=U5{Ok$km@a~5U4Uw@Q*b7CCY*J;jhf|8^@!HK;bn!mUH1AE6-x$iq&J6@F z{6Xt*G1%A(752g=Cj5cpCwBv!^1EC9j3)N6DKO5}F6M2q^BEEML|qb)1q_FoGbH3X zs7UchfueZ3?mjx?s(JNS9A|RP;tUQWc*<`9o!QaAVD4ak7eu)hJ#VEmXgXU88Ol?q zo8{rPpEKa2Q0`6S$p*SZqGr_fBwk_OokT9EgR+%qv@DbuW2^w_giFG(&fif8yQu+X ziLK$V`ybv8D8fE-Q^{>*=-kJpWJB(-QXLN_VtozfJ;7STDXin&#-xToWfzjN0AA8+~_eEc*#Ek3%fuIv{JuV@2A|!*Qx zT>YqzJ!ky;W#r%Y6d|5P@HN8f){i-B>g{SCJ{}>6*vxpWX6529KmuGbkC(Z7EWW=3 z`X7rLt#(2MCHu=reF6BD|IH6SNx2SxI-A9eqG5W$;vf9$E^ zh|xdU_{Nmi@OVOK8IJJv7tY0J8#_UlOW|aA>Y=Hz{-MV-UL??_gUnOetkmo4qRoq%mpTBNo`)~j!yXp*7{q$z=mh;+@F!T;+bS~Kay zX|#Gp8T~XrlytT?crfJnjewJSvG1j!4>;tsJ#TyB)k9>G>9b7#oKao^#)JZ3moR5t zBr}r9eMCimQ6C{4$y)`QQBf}0u$MJd!RH{*E@- z1N840sh;52A564!fj&=gZ6w!Q72OtZuP+z~jF5ZJtik)f>j_&zDEg&!16>dCnVHY~ z9Up)Sfzv|kT~`h9k_p_#?S&vK7v*qCITw2zlPWw?571fN&53A|jPFn4E1`YzJNXkc z-syOuZf2l|OkL!54=#R*c9C9e|4$KDZcN0k$Zyvr@YdmCH)rj$g!|TIrX4W>ln^Er z@_lmh$0Fc>W%_lUbN~72Mc~=Adii4{SwvZH&wht*lGR%IVjxsSj$@G9Y2R{iqe7;} zf6@`9{nD?p%Jxi)$Jp`Qnc`uP44p$UWvS$ZX^LFf(ETS2L7oB=9Q_huF9y>zX2hR& zI$TwrY;><^RU|^W*I13hzQ6h6CeCT4TfW$!4z*3=DO$P}zW-w*|1(q5Z0cMY1Tb_^Z;Y8%}*A{e?hxx&DjoBF_ZB-TAkKLJta)h`VKUv=Eb zmqgGg@z$kucjsG5mLZb049gepXZz_Z8-D^GVl=>6b;f$rU%#OhE_}r(NLM+8+;j9s zFgz6gq3P@tg>nWHLcjmT+4$@i@pARIEs#mkXyt4Eu`B=3D+Hh~u8*cnD z{qooH?f?;qanO)?Z^Lw#9b8*I+QNTcIzoH_HJKx8DP8mh_YkY^vK;mcIcdFkw?9kg zY&3W@MDxVn*T*_(btw4IG{D$B2WRB9Dk()RX$Ahk%E8Jn(L?Ze<=y4RfQx;_C(8m3 zB-pp=S*87}8UBBU4^jpOa4ez%ScsDZ>5)KS+gLA3e|LtnieacfNGB4*Tcj>u3ej%! z!xRzQ7nUym2^Rgy=O`thny9v`FIWbW$IFJ1M-nip`~);LLRGy$d1j3qP45LbG1gt3 zh8uA|%~Nt>4g)nmt3da&gZC@lL><;dpX`GPnPXh&uAWW6D#T0M;W ze%-7BcoYM;XfG$poSG!}{m;TGzTx&^;u5}6=4U$9UFnLJ{UZwNe5&Yx+DhC)eD)%aKp?&JLq{aYN8X8nu zs3WwC5_b zl(7E5%M0;&+n0}}}M4O+cp4cQRe1L0e^?;IcXD~CMS{LEE zJ)sWFWoa>9Xl4ps35;!OaK9rv?7KsrURqRr0bdW28`5<5-*zrm69c`)?8pq4xa>vfQfh z2xOi`j*o_PRo`fwL;mKI=HOY{AI1;=yEZR~km<3@%uL~D3~DUk3@cuPkP@p67Esz* zPL!tGO%Y-tV)2Fo|Kqwp(IRhvDDnn4z97W=8bOK{(8C^u%&g;)nKjod71e)vs}}OF zjygJ5;81_GPz9+~9N^tCGyC<#etFsqlslrAIzq@2p>jMg+Hw9R-w+HE@GvLpP>_6o zq09IPqRtck*LIP}Ki#JE5j5-WI(z&}B=f*y@Z^UjC55h_O5*>}u@51H9_N*Y{^uzr zkvX(iDl)+Q4tzoleijQSL7yIYSKp@~M95Nv%FY9t?F4_78}*EjTc|D%SSWBffc_Z z8Cv_*^mty#mpY%|+*$TnyteIZb01>J%E0WAgEsVp!_De#-7(3O!1N?0HY^5GXE>ZFkmiNOV)(BwXO z_AtOU*%F@Zu#WC}o{-R=9}ufml|ckiaH&KI9nx-&-2~nrmC|O^zg3MsVb*Q(j{6=& zHa`1Z2lQIcpP)A!b1)Y8^9slK4~~aTw%bPeHJN>K@|$tRuGHS|&Fxrv0zf(o#G_O}PGAEW(RaW>tf~C>XTGu2Nf0)P zu{s?#=ZrCBP$-ysiHYsIHC`hBxxS=3)lldm(~Rp;#`jRN{?arGa3#`dfPHY23p~J# z#}7n2ZT8su8j&vxvyGHl0OF`IfNI^kFvH>if z>@GEF7j`iM#lj0F$@L1Gv2IC^Se z1R@T-=gD>yqcSN26^pP=IEB!v0;^-MgA!1N%mNYz{@0Q8SwJvHhX|$aZGY-R4I8(E zZR)$Xz|Jq$GRGBZw8F8b|C630@2?!N`Om077wf?fY?@*!{^tDk=vRaCCSuh))0@t6 z_{K~=X_qlfz}ND0^`TVg(i>VC=mP`k~B*IK|Ky^chLZq zT#gm47EIDAPtGZILgv-g*`Hvh{JDB0AOXa&;BiJC^2lwSR0PXVY5uu4zhcji(oKK{ zQny=^3Kqgcb3z7qPf#O!{;iS8CH^Obh@<3y=S-x{khlZ>d;bo5D~+)#IeXgiZU*V~ zWpi{CzavD8j1`#kGb!LHdmL5` zBL95_7Bo_;#K^GNs44$BObzU8p3k3BOOF7%nz6x0=Kx2#(}Inxynf&#gWbQcR|?=| zGSiZxg0jQv&HxH|1`+f&={ekH#+$!&lK%@ZhL=cVVEFlm{#D8yr{{pDtcq{bMg-yt z&)>Mx+l5>R#MGO_r(=7UG7C=xD7O|^)#!bb!r`$R*?%@)YVuha^h@&JVM9mnx zZnqCn1_!L_JdT42`{H!ph%RmkKzFkh@_7s3`h51?sUkwlZFSC0=Ov0!^p;ThQ%w(d z7B9we)%yj*@l!Of7YEzXS7NMhb5&DfdUVU>%OKezza<&6=4!nS`%(oM!0m{2E>07w z*^`6ngw+vt-Lk6Y$kVB=efZ(rFV4wfBwVt+(sq96v~pk^#U_o_75}Uvt3N^>n&a_& zz*6KlWo;w#$@V0o@OB9H;K1$bteC6oYx09yT};`vgfGPOc+|j1?tc3H6oL_P=SGZV z__kR?4tu|ppKT112h9$q?nJmE)tO&|0lqoq>XKhE%)c*es%3dpICb(9B7*0g-Re?> z-G`377Lp-DLZAXU0QjT=*7#c2t8Rc~ECU_o)HI+G@Zf;WY>vCQzqMc3Y>&$UXha*P z<@S5TjsU#Ja=bVt*KP&H8p)`Gr)lJ>UiPa!+}p6O$2m^L@KO$L#e*if;#Wqu+#oPaba&8f z!R78^x%3upmIU1~!O>ISys@u#S_00iSDG< zq1NWb{YDmL_M)cN>hQ`}(ozMomdwYy4 zq)OVwXSz8mq^w)s`a8pWvk{_FG6;ig|3>we$k)9$m9B9cJbezkg_Z}GW6GaAH{q|l zqJHUzCUyxa>njG|=8SMXv-!L)WHa!#+;l?~l$>u28E)xc>UnMG zebtb;AXaelXH-)@aQtPLA8G|XQIQ$RN;mwFq|#|cK|1efqjd18_mvw!?Lw?x0019t zXRbp|u48WstdL#?THHE8(Q{@Mue)dgowp2}QD?*C9*<}^hkmt^ZfCw$!PS)c&j<@~(TmZ=niPM0ArTp^E#9Mgl?19I)uc<5^EW_t znHr>Iu#+ld!cxS_mQ5;W7*gM#mxeAaZl<=+vo;#b zs-DRlEPIZnkzC4AzXBifo=m4N{la7L9fh-|K^_{)Pu~l`L`ATLIwjw}p$Js&Qf z?rl$2WLpeoR}W?)}Affjgod_e)l$UgNm{BW)Q#=@y@EJYFSuFmMgHP6Ku zBe`iD$DxpmtJiJC*{|Cls>G4{;dyA!jW^zS%?J|~fz>}F zSkhGJ*E-q|pi73jw&Cm@_2K-%<3vf+fXCj;162OJH-dVVcAQi$8@3isYkjsG^SKR5 zY0tk6{K%t|_=Ni0?tq+GJ-;v#b6Wm?f1xg0V{jr??L4+c{GigtXIhn#R$WmHZ-5T6 z)7BWfzGABpFkCbK)t&?{C<4l$)BW%ooe3NLri4yC<3+Hm4UpFt$@zwb*f+8r%z!;F zc?%^Y#(NDi|F`mSD!1R1$utIIP)#Yo>p2Pc`5qNOO@6@F&{P3OWy~>DGulwhfnTx=6LJ=QK zI_R|e?_?Y}7w>nf(ksz-hEqfqC5>PDiMQ8^Ui&LH-48v^iP%kIokf( zK6>lwZG|UmA`Pw>eh#+ssY@*f*BPS<-rBZQTl9Dj!5jKE@cSji;Xe4TkDsY{DEW@$ zjCp_RESPVxhd=X^5q5i|ATNlT` zJ0k=Qhyu%rSTrNNE>A2Q=)u4XwAR6d0iAjE<3jL$gHFkX_kBB{H@bAbnM(hXMvL?6 z*{aI6heH_vJ(2aP4@_5@~8fFNpgP+p8wS)r9+?( z9(hane|RQ=4{eHbJoxT-UTbEZhF8Pcd=>m_*dex)&?sd0poTTvB1p1sMr*}o#BT#y z{Q*r&|LSa0jMPiWGZ7***t^ncsX;?^+QR=Q6Wya7s+j4gHxqF)h$@2x{e&uCi!wSoi zTq@!@PrWjmNXXFm^quqobR06e4#ilatMu-MS5(G0Tb!9KkGE&HA*_eLKBXN?Jv=?D zP3gH=HJMup_yijS_RPD4%~8%2wMMW)OOVZY^C1;BTLYlXGYTU6@D zpx>kVCPvW3%CJoR4dGI-7|k(|dW%udl))TZF+ACu=L9g_BfqX=4nXdWz^9XO<6|EG z8KhTYB%&p>`hDgne!=y_hmns=!qIWa^GFYSxcYyBQJPDVGf;ScLg$=CYul`IzLgo< zn5rs(WK7J4k9Sh}%tW^>_43WffQF7o{hx$707@y$=}fp22Yxg5I))$Z&gvfvJhm2l zw;q9P^Gzp@|Nlr*K>I{mrJqAY^bo&e=2zV&M20nvla`n>*_BT|u~N0mzIdS41MLKM z_(t1B`OG6M#uE{h-`jwq+Jp55D_@wD>upz#UzIW_czqaHoCmN_&K$1!Liip z+U}2QETx%I#7Aimw%9*qkYMu1gFAyxMHyF2Tk5)J)Uh?#PzFFog}j%0)egpFK%lMu zT_lJ;9xr5f1Ocmpeg8+}F45oZ4O)%zRn>S@NOGMjSKARSJx8O#HBn&I&(KW{JVAhZ z*gVlg7MOjhB3rz1V8Mq17z_}0*?X`o1CuR1y}*Z(q{FY_ptkM;l7m^#!Mv}17RZ?L zd9~$QH-P0$iAnsIdO%xFdVQRsfLF26Z8NvDN|A)mD*oZ_{IB7MCN}+x4_L~EW=R4g zrgGmhB?!7Fdt$AG^8<*3aG9Bhb^oW|+Z`E*}r9RH*LC%E1Y4QbXq>LgW9~>GQ>l9oc^&F ztL>`U;KCg~xnJ`ZGpiLewTGjw_ehoxK^Q#?nj{HWz77dlIS**n&;|czg8|f@&c9K0iaIjZ!bh*^=men%E|#$ zAP)ka@T4qsUR64NYq{rbFkj~^>$LonhR=QuH2!QRp`w@DT&gLcAi@0m@}>Xl%cppD zVXa8Jlux9XbuQ$h#cEJtnN}Q-Zb)nl8Jy7U{3x%^8RL_~A(7fAP z#ZL9;GjLz-%(nJwD|3IyO)OGh8K*nUku}1;&x4v{bfXjzUjK#F{Ygs)cH-RR17D)GVYEnVBz47+ul10dmZOp#| zw#F-wOPKvt>9%8Rb4-f6P*Zx(dk1T zP^W!*`jd?wPyX-QR{k$T)18?ClB=pmK zA4kZ1!`c;Dz9_yNvcWBmo_CAu(UE*5w zc}n1Y`d7#v=H17<(c-8`tWzR@WJO=M%oH|KRB&UXpDBaQfcsV{V-O1g+yRlBwH|YU zc2U$+m34vA>CW_8w1(E3S7o2@nvp6gT_j{IG(^7!JW4aL=ShH&U!hx@B=s8_UkfCv za?^z*qkN(Jf1#_^7~tYGUT|Z46n`qoB8;kIKVh~}jDE5c35++-Mv5WJo69BRXmd=R zhp(;EyeF@(BLDe-CU2oW-LJTtEGxs>9)_dU8=rI?RXJ%0u)3HWFV9cw{TzngFaq@NlNq$`}R1B}A=!E?X^K zvi#i!bT6mW7#tD`8rBmNuc8?@&>MgjXtTpDze=(=6@#v#F#sFrfOSQ7gb)LwYpp#* zfyM=Np}>qxT5ax$yEM)y5?2E{rDzg2Q@~|hPL%qh(!!C+6O-cqJ$d?ufnZR8!ub10=)Vq7TDEHfj9EJ?K;OPqi+f;n4>z66im82iTNKCjPNDe~E7HJ{;Il@k34TSQEKLf+S z{h%XIo8P%OWQTUvK3e|M*z{HvJuIISQkAe+A|d6rBB8I+FjK)xcm%?^|rZ%?#VH3=PHb zkdZS94Bk>f+&{j&IBSYN*iTO> z-~_L@N9D&>sn#ZlHNQfQe0kvFg=aI0spR4budR5K-mKueo19caAA+s->>LrqI>!o@ zA>1Rsqx89TPrmZ!+>7II9l@dvou$x793`h;KCt>aj=slg+d^_QkbO6fPBG<57qaO+ z4_WpJ%5U?v`C)Hvtvgam-YU(#R6R6YbbC{lBmIrNe_{cRm@A3cZdEz}We6|7d5SgiLnyA<>drl9FmfnH3VBVXBmwW_D4yr7Ez|Z2 z<+7Yh4@=x9@h%p622@*0cw-iCsU=@$@eX{%=dvWCDaKPIJAGnaFgyUK>Sr(g@XXFt zAFLgE@&`-XZiQn{SLOq+aCV<8o~~dy8W^Hi{8AR(4^n|t?4lt|Xp^V{CTEfB`o@+; zMC^LGu_pmBRJ_fi8l&{JHbB?bPiMpyi$JB~&BNq+Yc8)-24J*@f5D!XdEAaV2Q0gA!DB-w!?07d_J2&s6eoJBhV@4695XCz`H$7?#Ug zP2%U74-MGgng}JIci=ECiY4LFQs66>U9@P8Xch0A0?MkPqe!VmX^?9(RB@40KR@@Cf@CM;cCz}e$STMd2&ubV(i!oCUb6h4z+krhCk;KF5cORhy!@GO5UU7`iuEG5NQk*@-i` z5L2q+V{r%q$xX=0C!AOXu(6iMMh>n41KJ5BkGtFcpHt2BME+f;@)J|-KWp!PovdfE zW^qftg@)>&OJl?3F)5aRW#MD!zjCV3Fu8$SoJoRg4uIeJ51?AM)O&Hzc{Hs=Y_u>K0j|V6aL2Xo&AztlD_Ucq#|iw z-6`8W@q*8JB$IFABLgk5|B0cJN&kBO66cQVV~{^c-dUAeGa&Snz^kD5Q-l)oG?V0! zp=69JNNt(Ix7p}D1=^T$OC($9K!wKzmo_M;HTt3*)v&C;`m*hU@U!0n1ixPd#eX;oB z=!f7*%b#uzX2Z0PdyVAHms$qoku(ny#L%AyvvQQB-hp}KBz#J8(lgE$Lfxs{hXJ;ZBS6d@0KbXFa`&4rEA*+!6i09&4sT6Cn1;5ECA!A z0;1KABDul1Zwezmy^nb>-M%&xLM@!XBtWopy=f18k;;17Wm*RcDLKQIPLr*tAW5@e zjTBY|!49ReHmyt3Yx{S#(ioO4-ETclGo9<9ntnd~N{-M|6{J{Wh{{7R(nRnQ>CK03&eb{Yr*%(p>n zqUcYhp5Ov}%TMVZ>DD7HP)cTu^c6Z0G{ggUzD@mPFl-C4Wk#vMxaE{1oyxF8v*dxXqL# zR^*z%{vx|GJ+g7QIWnGkgG@b&hbuY_|F=rj2?zCdXH zQ<^*uou=vh?bYQ77S(h(UUNB150*zyUR0vj-rJ*y{XN2j_*;yRCl*7g2FY*=nRN&# zfLadL8qjho>}5|M4Q23fN{>0QI`7q*y7e*IyuILM1h)saX2MF^r4&0p%Pl2c{tAjs zT#8h#+M#M6&571T)|8bW%%!f6MI1&O`c9o-HqT)jR43|`ovx=Dj+~#!dRJ0uyr@fg z#^M#*K*4o~{D-F!J}bYNqr(=LUv3(+pHR)_ zIa791mF|!xNIRvwg%X%UteaDHJ%x*X_>H)G@nyI}-Y(`=mub>2)~$Zm@0_Rlol#@D z-7#Q8Vp?g8K8{!OWWUL1-OzgQnER>K)c}k;2%v+)^Q0;vwvn512C!Qm$!5BR{sep~_v!(x8T`a0nI` zY3r`74J1l~Ee%u9>B z{<^(tw51`M2Yyc9{bL2DP)n8mhJs2|J!)Zx?qx^XRJhf-z}c^0Qv?&8pSsN<8!FI5 z+BUy*ZR>)|>9u#~)6U&zxqSyC;`C+4b@SQkbV0X# zOE0-GVOUV;CD*lHtc%50?k9?RH`SNOWhQ@`-VVuG0fSr}cUH^O<-!Xz9F@i4s3ogn z!w7~87G<|*<4;ODOXJgR$?b?N%+vYl;Ot&vxthVPz1~fw{pN=%=NTdogYZz@7wPJ& zQ6!wk&7^uFDO`E=EIr=%>>fso_n7C=UwXcc7{*v_Cu4d^=8CfU32A%Hs!8T()gno+ zl}B6^OH?LjD7*soU+HCa4~4oA6(F8GjbRZB+I8^0rkkC<<+NZWia5w3Wju%+x&=SH zvw|2=zk-LbgMfgOk>u2pOYOT0n;xwajX+Sw3GCd-<=;O|V2aBXA7fO9_#3jU z-1%)AJ!4S4dJy*Gl)qK|lVs>Ik)bfbY=h$Eg}543lA*M|2zD*T&=WtvQ5?_COx9LI zO}qd4-eKs}BNr0-5lk32>I&iXYSql;b|^MC0xCI5yl^P{^l1t&-y@H@Tp%&Rv6mQ8 zM%(~VJB$FGdKyY{HFRdPyP#I63vN(7h3`MG&t;Wev?0|t67sm9$8B`Y9sfQNTg>d{ zE%|m^BcY}fw($*97ufLXH)h-cr!plrf6n&r9*t}Y>bRsDZMAWQJ1|+?!MLy3j$7=tyb7*9~f0^Xf>G z|1hjI@a>T)zeO`Ua=9Ag0@as-B#cBQ#I#>HFzs*(2ErZQ0+d4t(Qs%FGtK~ilMe_d4u>SnbCW zXc);4Vt?#SZq}|1XyAzzDUlGt>`+KrUthiCg=kLSG}iqL(o zXQ{3}vv6*EXLJ786;2$ibI8uEXhtDLzz}K~hHeMN561xU*^4N|NOpM!|(9SV=?FYqd73&Nm#kp1_tF!^Rxk+s51pV#_6PeOJ zj!VJe#nyU_&mK;Ww{;ar?k@v! z-llR4>8%Ny!OnB-ef+CF7cwG(`wI^V|=JgvJ*c-d&b{+dTMpFi-%<3b?rmJZp@Lf*sIXG0_VkH5ei?=M~V z`c$ForP<3rf7_=A<)#=)!CYiLxGK_Zq7uEki~Gnp8x!po zAiKL!{#**ZS8PSSH;U8}MYMpjmklr@kECN)@V#-vyqsEN!=`q9=?B65MVKQ!T<57Otdft)nrGqU7Js);c-|<+j5yoW85)s zPL$^}Z=G(MiR&(*SYvM=sBn;Lyz4pLg(doJqs(o-D&2XUR6Xfzu=u65W6CkyWo@tM z0|A$+10bSPamI{`PRFpPCeC!@pQFu$N-O(NwoloFbnNA^*p>*vgljD^a%G=GUK&+9 zuJeAWvMn@r#u^hLACr_1(H&PazJ7Nr8&sklIEe4mV}C~0JcSO5<&G3IeN-XFf~Xx7 z#b0^+v=H^#saU#8N^j5R=qUR`8q?VQ=0rm{lLa|>&7fVW~D<$sWxn7mj4CJ`r`lrIu)A|fIKd%4yr8czGKySB!|oyk$CR_fpm^NeaS=`JzNvM zA4@sornHA;Tll^gTOU7g@}A3b-u6TCVx`-A3bF(FHRlu`n<0sSuHIW`jj8t_6TY3G zIKh>0sAM)vL2{zaBENx7KaecO<5*1Ok0*ui$mVD8tzmm2gbH&Y{Nh69qoLM8$z(XE=399YvjrF@f zQ+5riG}&>(3%mhoi*Unp&dAF;=Mq}0gfj-b@)5)O^1`H?6|T06L?F)rIOG_OgWmvA zz^v;7$>Herg$sFL>usfJWxH1r-tdaishc0VtwzHi&g1Kp_M;0ZL_*H1Pl0prqDYFM0?v*$`__wkZ!pmwImI=}V(i+$Zs z?jw$Cx1_|r=$(TU3ZP=SKwOqT5}}&{uv=URijYxIfN27qG7d7I>^lFDQ#Yppx@Nu( zjyd5mxDHo&FG15%Bh)Do__w5x1s+O|VVacvy!5(q7`@*Y)U#b6qxe+m<#wSTX2yli zfqTXR0u-b*4$ge?ltEC+$inDD-swJBfoX)Lw#j z)}aZ4^zs^9dTA=L!^^N%3B8Bf8ETY`&?4gVZFl)L+<@Wu>$}zkqaY{LXkHXj76rV7 zy7~!R3VOZW`Yu4GzN{UEG-Wi*CSi#Bxcm7d)z{Z5&uk=}iN_oj92$1#g%*tu4bApm z5FKHlXH|bHvb?|n3DSBD*$u#Cn}a|#ZAj3Vl0%|KCyxWVuV^(W;0l-}-+-P|c)W`X zGQjUn`f|8Eh(-$mo_5hn2y5(%@^M7*9ErXVAtIhK@5CM78m<{bgJAv&LQWW4{OBQUp-C` zy21sF40z;6L)(#>8#M2X+FPp7RY-wY*;uUt!hl_N8oa}6(8CsZC!+d3eThu^9sRuW zns@flIfT}7!oF_0`}s$poMsM{Sc&ygF?xY|`H~Iu-9i{{Q4Mt|^AdFjEq(8jg2NBYS4>pj= zE(2JZ;*q5V_D9fU1U*LF#4o7M+i;r|wU9t@CzW*jUFj`ARoo~?&u7B5g(4f`&IdZdRfD`&#yQ9_ zTirur@4sKWvI@5=Pz<$|A5AVq6}B`1dvlam!k4e&E#mQn8J-Zmt6<(JT>tS)3RrbN zy)sv=Q|+D>5tHTgABJRK+=IqaH^{69^`ac|+^MeOVe$`@*c#RG)3}fkwS)Zp3$~p8 zG)UIbZy&UnN0{MLlCz;P*S;RUu&2Ep541NM+-V5$2;<3%%Q=uuUzDi;EVjx{=S@0O z@FvEJdrbK&f^`{4FFPY1$2*T7&Na5Q$*v1Fc)D*B6;kgm&-odbEU*jV{LB&loQsEk z1NdQ?p?~^KMuT^MmA98Fx(Qqr5N(|MhIso0p@%s`=|v9A!bhiDERLn#0sCib!3veO z3$@(Tl(OkNuw2L1p-YXv0h-33N6<=FwbO`H`)l!8NT8`Y)v|ZVa-P#keEXt$$I229 zrNxlJbcqiKYYnL*(9x_MNPw>d%y@P&O%O{Y94rl6GF1=6H()tN1duOMDE19aZKJFe z($uyUqL&JlKR6f-aMP~^$7*`F*IJ=m)j9Vzh>Nv*nsdT(#6-vh85Ms3K9qZ0Ex^6+ zxjt{1!#9OqSKo!Eg$lu@@%vj(PY}=wR945KLa?lJl}V%w-SZg7lTt~v!C^fGSOC7#$TyTR9(&0KsPuqayM^I*dN5EmgJ3@6Y%=aDQI`sNElmDAN6gM{pm~xw*zo?qNivOZFRC1#s)1?1M=xE1NMQYDJNF(buKFp?>d|~>=*%I;H zQ?bMuz7ksM0h17ep!Mf2LFA4wq)7C-EsOB-y$jD&SbF(u=^%$^2+64%*XBBt!P-v^ zS6A1FeaU+7%HRT2dq~$c^{GqU$&dpWmxvOy*3%W^Vje+2{yxr7PupLf6(mBffB2bs z>QQ8WxSGto~KLpe<&JiYsb{KoPSJdU3L9CS{J#Y999IJbB<@)svqWUtdm?5YUx! zdqWvOuHPz+-&qHJSg7g#W-k&%^QTXV;8;_yHpVH@mw@@8GSUp3*{A6UVn!eG@d?fL zhTe+?r{~Z^>z<$F2th6f8|nMAOzKLI*~=#3HaJgs=O$k0*|A~&9)^ltdgIjQBr$#H zJG-IM+I+QL0oa!zo&hzO1VXkixzexjib%QRTj7Y?Z7$ES0XffS*Y&(f zKnhB(jT{|Bm7=u1s(Vd+VySj26)Sg}=h2kfE-KIQEapU!fsbWaVq?kPa78Z&FFonO9kSF_l8}ZGj381^aOZv&Y;>$tSRJNfDxH5-lU^Rm8)$?kTaJ85x4hQ&cCh<_yR&uK65G^<#Kb zd-ZdVaN8Jq|2W^l}&CbEfVv@}6h zE_ZCMc&YsEZ~|}j)+cQPbDuKDqlDUvQcESa{TtrH)~i;EN+!it(YLmjdtzvVe6RDL z>jL5&6))A#$~8nFf-$E%emB2zLzHHPMI5YD(}DOm8*8QR`~J;61m&{(C(!L6xVrTL?p|->1B_gXtL;(ogcEQ*v~q!K zXc8d4xG%nX54*R#@~eyL3)uQW+z$$6(ZxT@XY>GyGNuuG`t2vm(XB;$wpxwCu)k5X zdu_=|H3+KZ5~~e^pAzCgf6|EBWZ1JGLBSYBKC2=31wD7SV^Y24lm@2Es$hMC4T-3vg^gUaau}853Rr%6Uyqx{^j@(rjSVH2`=RW( z_9NM!vPwcYEss`@%spQ&s5^_AbAxayLlv4wVh}=%4A-IK3l#yr)_Mgz;zhpqXo~7W z!mv{*&?Ozw`%ew+Rxb#XSrKr2oGMa-EL`=Bw9c`Ua7M`eiJo#fgJRGleZ?i z$q1sNAugC7w|8`EERipy&UpUi@#2tjru%Q)(itHPaf9+FbgUS57QC7hJkFIaD-gK) zJo<|6$VXswAv*rKDRJSPzhc_O4r1**PrE9Bks@(dq0!neBM&L+#X>5T*9I@Is7M;m zC;0qq5#Ga4u^RRCiDv`yF$&sY+H`vF*7N$C*vjOeAJYdOg^&_e-Fxhz;u(Hh1fL$X zKTKelkXE?&q2XuK_!mwnU-+Bat$9M_*xxd2kD4|Zm6R~H^t$L z5Gh%^0~tWWKwlSgDCl3>**qnoefQav;Lwy=Wyw+lZyu)RR|958DWqS=Z5kl-9HZ%W znHetuO>rkIDF!p+Gyq9A8B+0EEG%nG^A0YP(=+19?(|Hijwv!&-7z1H{;Onv>V(7^EqtayN#rSwbF2cEal z8wrks>P8*z(9?w-0iv$2g7IvcwAIZ;*B`LiBdx#HAEir;{jYxBtURY=mfMj_!fR@b z*fooc{%Z4&Wb)@f&;GLV55hj9YXouw*$A>RUc4d$?@LBWQb?alL4vr4<8!$iis<{T zwU?pocdv5?;~bb5>0Xf_jFXTm%Xu2|@FB~d#<+=OqgM!^mmSGgU)EKs<86nI>MPfL z^dz)rgM2J-?y)h6{}>UZQ0dT_uYy%l5S3h>bAd{O>d-C7en&%;@?HIFXNhGeD+{I-T!6bc^i92&bj^M1Jv(B(1W3hm!Auism?%fb03%dnl5aSH@8{4lRic}Bvb9!u#~ zwDZ`qr(Dug1liB)&VY+-WhQtLRhXd`t>3O8XF19{D=E)M8uvO5r5$QLfi|m z;>VE1IM#fUu9k4;7bKF<+6WtJZjfnDHpej`@fm%u?Ahb%a$i|;^y#H#gMR6wG~A%r zcIVb}u%KlAexJD@85a&M@th`5Jqm+7$M-l6Tj;PR^+z)WpA)g>fv(?!h}l9$Ngo-% z@~pG!9Yp9xfU;{jj;1<F4;zOfS0Uw69}%k&8qq3 zM3T~o7D(krR>RM#{ck$p*ye0^9%QmXD!0MeX9hej+8H{58({#=>0jzadi^69FNo33-O$~oHBISvSD^Z6XljPens3 zFV|OZsB&cX%v#Fb*2?WjX+IHO3`VYrqPZXV%sduG{I28A<7{bKHH0xN!R;iCAVZbq zxAGbA85Ik(MBNvjEIsknsxBW?8gpLV^pY2`sd(J-K#Nw73#eUAG=x*Bfp0(RMs=w@ z_`SodVMUukfBo72!}J9sRX`Vz_$YxkK9!^ke0%9Yv!_^TK`-^w(feBv8&Jhtwrv!)Nk$_4Qth-Jn!vei-5ZLJ zxoU6;zy^4;VVgZk@4DL?y2F8s1?iZTB7RqO;M3!64$>^R2TVRMfs7<6fkjQH85wmW z-yb-!9qj3#P4>biPrU}IFHaQ-lv5-tvrplM@j5_OHKWAPp+%x5XYH7M6R>!e-dp#U zxH;m>xU?qd=%V*rPo3V>kV8*y7;)VQpHxnYLm_84tjNyD-J!N~UW=3q@^Au#o@8F1 zxE9=JA==KF0WbD3AX%UHRd7K}7X^uL58-uVNLrWxjAp8HZt#e`;R6599B-g8A<+^F z6Hh@AEbj1eA(wGO+}T^tU4oTe<~SY!>KT(Zc@3;})WB;11}tBEZF+R&p7*OTUm7^F zv+Zf84ouP-LP#h@w-O=EjPx&KWj09c+p71j$UjU$?I)~Y$91@d0!13gP8nj#rWl@r zZk7lDl*Pp>7KUMq0P;8re3^}Lj72Kh!Ka)(`6d?}FB#&^=e*YNAL_AB(>Rw>Q}Ta-WKLUSs?^%fLr}_CRkn8Dqg@c>U=|k#}6Kq z7X0hHpqx1&pBI?@TeUd6Yy6k#u(Ck6>V5@4+(88a0hqJjI;n;pf>e45>)*>G+&o9H zL=r)XZJyX`C9Dq)0-+g&{DGocn-!3tURkCK0U#}=fgwi+Pr63DfU+Ls2Os2s-dcj_ zA)(KMT6$~gaUn^`F>I1BNHcj6bXJy3U(OZl?G7@|1P!L^!X(9sAv~svSA=^~_N9@Ydqeo>F(h3Wew)J`d1>u;(^d@3Xpzne1t=8P)d-T^~6(-VAiVm zv zF9Qe#=7geGB#_p7GAWn^nDcNzar5l2jX-KvG28N8pA$a644;zW`;*^qfq;LAnwl zF_PE z<}O95(>Cc?cL(B|XLf^ccGRwc0z|A%nb&ImB{Am@p>&r5aVgWdSf37Kfum4%`Q352 zkN}tG6sd&fHPxe*Brb@_84P!Nx4*BK5d_nto1%hP?}=cxR~uz+Qtp342$js7?QaKl z?Q>7V&faLJmq+o5<$|T;8oGhHTSR5!)td$oOG3@J9u+_R>u{{o zVVQ_{O~t!W?zoJbs?EquHv=Y z*Bc|``)v&Fx!FN35qGng#=I#i9HPw)fBY(spBW>~&|iECu=_puoq0^5TuU#gNHa0B z4Ww#0=D!ry-{8t|4P1kxTt@)<`u0e{zVieOJxJ|@n4(3w#t+fg3+<)X_VsoBp1yAQ zTVMAzdc{I!GTXT96_WiLuXEqo@<1p&>9@El7-mT@jpF#smnezUp|2*OmVXUWQIaPg zQK5Lqcf;iTKajJ#M~|LFeL_}(3*#j)ZyKG^x4FHwsJOn;Z-1)491pwcA*s%L zpokeb1Iwk`tw2GH+FGuf>iH-R{H*Qo$FY%hG6?Rk-Mir|JSap!(6i~a{j7_|{E zojVlFbO=>yt586QeBjCd%Lm3p>8A?8;$pDH4<`FK$RVlis4bJ*nWN;-Mu=h*U~NJO z;~<@p@p|LHDa>&7JPv?Jt%_fE!&P}bG@nbryH*9Od~1#nXs_sV4U1J42*%#sBk+#( zPeYq0bdP2vxk+yh+?}s(ewFVAD_EC8LoX#{ z?`O|<^?5v+UN{%vYglNZu6n1q(_iO@7nof>_XRU149Qu@uo7=|B<-8Unu{0^zj8FZ z%RlA5HUl@LwF?#QSAqYc2-^Q}KwQI5J>gtnvH7rZ=LV_vTX!I+cpyTGJ%lP%iRNU1 z^4Rk!XUK4-xmVvCJemf<>drfsB?f(SxK8gVnbyk^B+s5EmY+w$6nb^G7sL;zbwK5< z7tSVT34P`dV5qen&nR|mgTlEg6k}`xUb&=&VUuC@S3vN}Wk5phv+j6GQ&w)j6hi?9 zLQuisr^SIO{Ff1Hsnr;9r7Bus4r6MZpq3?((Ijn=aZ_}Q50GCXke3Z-)Q{Yj45gf8QmOr@8MF1cg+L# zQnd8!z+uppr6s4GsWIE5*FUAhungJkJ3hk8;?YH3=Oh42r4lp@igwDSaG$b|o(xIx z;}8U>{$}Fd*Kx@I2PvUz^lVb}2{R7IOsjh%umTADU7l*3g7Q#F#gcPI!9dZC4#qg3 z|IhY5o$6i5c8?zYvL#S!lwfp&!B7R504cAyTZ8UcsyIXntH0j#U=m=gW^hfd3ZJdu* z%p=5r0KkumWYeUIn<|0MT4SVh2FWrZK~v9%+>UHn&Xr?!otc`#{b`v}?Zd~~Fe>(A z&!EF_oPUu8W>g&*hrp?YBzz$?0}*c_dfs;BQDioP6?c~e!`SXI$MmX4d>}#0%S-Hij|W-A=gqo8(Fa;PN{6PLe!UKB$WQExACp1UvaX ze0uFQz3{(|9zL7hgU7@GApei2AYp<4^+d#~nUe57&rkIJ@xu!ZpmV*iefPNfx6B;G z>VE7@BhHnfwAorKf#a?RPS(DDqwm9_7+<6XpdPbQ7=7R3&E3Dp`Pa};eBhVJ1!_x# zcfQSWdq=gW$^Rmvk+wmA<|T<3J^wclbHq2QXDqe7%3_ zTP1oMWE-*aI^PCj;biV-$A0G>h%gZzW_mP7k;6Z?Hy#7#{fUwh#Q&)(!U4=>buE#} z?ca=Y6WmI?PD)W4o7jSzhKJYBj~@Awi2mNbuYJJ2vp;*_TiDx4tHmdl``EC**NhVJ z@;-<vR&V=Kq`1djbIn@bS>3v^=5~)*xb58tH63et0eih3)+|!h|XI z%)5ev^mu=404)|86N3v-_CW*Amf|bcp{Rdn62pBvmnA*Z6-LV=y#7#Dr;+kF3Qz2h z-$nkDJ{{RulF8~wQJNpsVqZxQ@1e8}61WK^9q;*zH1_WWZ9EXAh~cDxeMsM5BIrxt z2fF?vVC}s75P~ZNfu(akqQilJTXw>lj^9@w{dsTfXOuziVsD83e?zvU3oTm4 zhAO=z3lX#}fX62Pvyh0G%s*AMGMjAG`eRo3NJD<=iF)&Y9tjbk59>Nj6ZikJXhWdr z2z-bsEw>=-A`-SUPH`1FbP|7y0tPh1S(y!}DT>nMP93-WOM3P{EGQv5lDXI8!avT1 zANzso&|1qFk2$O(<3R%iPjpTW9s2X0SY1Js(diD?v%RM;e=YiBon#Pe&Mb|;&yD++ z0^9rWa9^6cM`b$qA+&!dig9<=3P8uGL1is1d18HltCZ6 z`H#r~lohB~Jn1B~Ptgjr{PDAlcctO}i7`MyN+uw^ZV>U*XC@&;lK=eQLC(HYOXGy~ z8yTuUwm=+-U+J)1|Bc3=hsK^gaU!X*EHghez#xQ*`RV$vGPgs2Mpnr$7Kx43iuTR< z?`+k_9IXirIu)^N`Qx27C7|}9kyniVbySEGcF3TSLeh@B9hUweM zfAd1WUIbbl+uAT?_Rc?`b3aM;*REnHk0iMbh3{USR~spg-I3ZU-g%|?kcBxqLWPk? zIg2~`Tu~UiG7IUcBI{08%Z@7h7$GfoBP|ZqbP_@$PNR}b*VfzIZtr@;$6cQ&YpY!K zuydO8PU-nL50$spIR1J5*TZo?MTh6%dfVM#Yjg0c9ouD2)Qv}?q3zvA<$DB!{>Z^c z>Cc$4`Ky=1!Wh>(g)cy5# z7eWsG-u{RhSkYF6OYaX4!X$2?WgT9wSoxZ5!pXU^`*hB8J5KvIVxj(J(}Jk}dRWWr z>34#|3!{fV7b>zEthV*UX*K2Qp>ZJ_DcyxxTK!giYfeEaxQ|-i=kQ(>5g`M&zczXz zseJe3Kc}3gP_vC^H|iz~Xy+mJjiZOwH$B>Sld094iCF6U!1g4?VX1+&Lb9>xHj!yI+on#|;!RQ%`S%f!z?k21a28U4<3qRPAJ|vZv zEF(KXPQwlF3C_ChiUuB%No0oSf_*os2j0)y`FCYiYW+HKXlzPcXse2en+mMA+siiF z&Kz1!L=@<)Fb0N4SZ}*Ar7O$^r%4?eo3tTTsnqCzFyG?c)6YsZvIS!($m~SCsJU{r>54~_tD8)75 z%&5Qc{Wk)OM(zilqsj;l3fNB^#xr&oM(z^C$lN`C|3NnMp*7!M!aG=d8h}bXe@yn! zq6VV&IFyac_!Pb1S`l4KXsQbticF9>^skV?sWBl3m5~469Mln}uui>fgQpwEeJ}ju z<ugIrL@CNpw-m=m&y1qVvZj4yzc@7)Mo+*>9BA2|7NZI zQ0P-7Ds@zX7~)_4eC!B(nK9#SkRFK3efUdX4~$QHlk=EAD)FbR{Jb6`tK`PhNG%NhrTpp^>a3Ob|u(qqFnmLpSozI2GpB6u}|mQ3@n_M!~0K(QYzNv z{G3I3WRmaDFYX}&nT>wvoc-5BIL3qCh%9Y< z`G>^J!)o%spZROy(h7)*u^{B`K4i}J-p#P@4&YH5ygDQveglg1;d{$zU*W-cXacY$ z|7|&iF#Rtg?hu3M+;HS=^{Q-^s-z3L| zXu$}1o%$1xsXg< zoo;}eSBZVtbFD$4b7NBSop&bQ8|@={RJ1%jlUau{CWV5pN8#n83T}IXQ$vBS6(^T~ zc*|DJT$+l%rRntjBRI0G!DQZh!r+%h9Mr^kzDu-23Nuy-p1=Q8A!Yx04CHyH^o%Ln zGlvajAj(XpA&J$#bol;(pv9S+YED+WvMIkUc4$ny$H>Ye>p4r?x42ABfFQF-D0y~0G-x`@Gt zPL|~Fdl@5Em|DopnS5V77*NroMSG#l2dEDGCE<~n1@zZG-RRKYn`qEkWDn(&h_~05Ufn^&=#j5H8-p1I+C)nr9>w6ky1NSE zfp3e1J~?Mzh|~Pmfi=ucmahtgV;=XUAM7~y%=5GwC|jYy0<9}EuT z>$<~gfI=3gAb0$M-Tqz^B4Tt6gZnHN+B?kC_Chu_-+A`MLk>C0d!A#k$M_Wc@3YTJ z2_v==#pT_fKP`f2)F1|EmiAR~7uPD)?VjAW-@L zOI6U+cVl*aN^yPZW83(1-s-T~&-2L=QYv3-*&4Gm$)hi57Vd}@XPtEU)p5}}(Qw~B zaPPSvIAI}j^tE8R6y0xrr`lJ$>c7>d!oN|QEK=v8u78g^em7n1q_DPR|As>#sp?}6 zF55nr7{i{=(RcCBvcFpr)q3JRBZe^VJs8ZneV6r9fpTH5=!vq>nm$4MhIYjb49VVx zmz`tpBQ>xpV!kxL?#YjMd(`uo-i+A784i~fQNNxBe`z}=#J~@Hh@SM5CIQ@|D+W>m#l;l|7^7+{&ZEh@+n$~(^mrA@#1YN&Pm!vVUZQUXtyQ4yW!jy>^ zy(}0p#g5ea$tTdY=xZuOv7d(h^uEzo5R!Y_DPZ!$jASqCdZ^Uf$j8l%2uE(FAnzt5 ze?BPj*fgbH-lLej8JIco0G_`I6-K_T`rnv5e#Y>iG*@}jT zd0N~r9IMmsF)yE~-dZZu$u`hlnH!|;C6l$HC*yPCK*VcYyF#1{1D_rIf)gaWXyqO` z{$0Q}u97lG@cw>QJ*`)wTC(V+R*CsLM^_$sjDNt)_ApQP&>%*Jx9@q}x0gC_C&1NT z0`E~p{9Nd-SssLEm)rSQM}0$FovO-LV$GPl1fu7vzPvOkvEP-wy|H+&X6fEZ&n&fs zN+q5*Yc;b1ib{AVG_s!pFCnD$kchZzw!7J{QmXrhnIY z0po@M#C3|o=~z-iiHyJ@rdwbTp{Hrc4?F8o%TBp=^L1&}j79U56Pfa2#Iw=-@Nim) z=BK`@JVz-V`%B{0>-v?be-Br>MiHDdy0BfT?add}>C*w`qgT>QJ-?9h*) z-4{-*SF+0_&Pk8xe#YWbem4=Yl)fLMa3(y?4c|AUV;j^7%_h+jjO}6F?aDL4IU(2lx^j3%gOOB$TGy1Y!=U%fRzwzjjapT2OPO&N=8Cu&J0N^*UT>B~;tr0ebyje~#Pn!rcH zkzZSx!lH5Eg`m6n&_L35v{TJp-KE)=k7u%jH?|Qe#HaGn!c!#gqC{YwXU)9f& zV!zo{)dSth-0JZqN*DVxgh?Q~(JA`L2lE_6#uMxzDjSv6#(r<|QRTzjpxAg}UP`Z1#wDNay5#4NIgCtOxo_)z8a{q`wY!~}&VGnl%F{%J0?R zeY7c=c=$$HYx3a<8s{*QqKOPDMg8i>1KHmyPhDHA&AfW?vB~XdwfHlUMm#hL(uqUnv4X#}`60d)Xfdsp*CH zlmW@6vyZfeM#4y0tS*paH}#}njPxwIIV#Zm(n+&ya%eNDvwe7V*vp7D#LL*!$0hE2 zz1{h3)e>Z=4G~ZSvx_oaJzKIn6`Y zl}E~aZl6hv3b1p$<~+!i`d3WGmbk1w@a!)4NX7|y8b}X)Yo?53$o?rXPsjW3Km8YkVlbOe8F@O8Dh{F#|kBRJypV7za{koTv zaxE&^PR&*6w~Fo7*6__Wr-uHDnIw}S{>bTOCr>Hr`liKTkdD{;b~wxD+tlTAlIOK5 zGnR!1-k4lB`o9?a?r^r-_wDED(pFU$ilW-0MriGtEwz=}Gqh&y1`#`tmJVvKs!fQ* zrbc3RFl=Qw`v_dSmHUqO=3ec#u8UFUUP=UI!e)R-|obh+wa zQZIA3D6(2napuPD`7D>0uc}%KtExVR^koQZr9~|4ZuQ?o_aJ|%;!uw8z5O~wzbO)V zb86aF!)hgcV?3i{q&_mfYk>w}0Ids?pVkzwvw@2c{nBxLr2SnKb}a~&{kmB;u9HiD zO5H0BJ^sg5g)5^II+)8sE9HRT^Xk?0Bq`XtTHI>i;2~4p0Tjy6fXD^{$Gn1l4;kn>VyAzN;lhY<)A!H);}OkXy0war{{}=mX|~4-d8o36gT( z(iV8LGf=DtETYbiZi2mTuiI~to@15>&jP}z+ls$Z)I)|@T^(;BDxIBPe`o=h&zdte z&G%``jjpP{dM!y$Os0F<3hz;=?%j=vt*eP?iGg)^$;9bJ;pyqOYUS3mPierJOc?4C zN@J3eH-K5pq91)2i~qSVx+pg_%l;d%O#NRi_#?m)+^a9FSTLv2922{+q$HGd7@;D# zHGOOGtpUGr_48M>ti2GJ+mmrhTH|Zui;uI z&9==T3RBLM>zRm*0(0OR`8H*?(O9ZyPZC#W43i~EDDB?PQv#V~J7!Izg#)=5&M-fm zoqna#tHjWZWRX<4L~^H9o7Z-H{|nk{V~NK^YzcX}I0EM#v|d z7}LVdjBQyrlTcQ(liHfGtpM}Z8x<^{ zZ=gp7j7Zxi+v-GV>b9+ZHY7L_fgtTZK3|Zr;@a~V!33zmnpBc(K+O95%KgzShyqrT zj3j{ly}B7H0sPXTA9bEQ&@lGLJW_#rxB-hzkGmNu)r#a@_h0%Tfl_<9 zU^UR^^lc$u0-(-ywmsX?rTY(Qq>8Ufdd8WXOb$P6v>Hf|t9tAy7rQ7CWLV(W(6RdK zub)94)~Wo4MUTI%L9&a&6KiX^y}B}vlThen{HWUOvRmPyXr@^4*~-~O2>BvS-|;Bl zWA3_!!TNOFJ!88T+nx)I=%iqRROd?$HvWpD!sL<^kCnncx4|r&?{Rf*2WQgLNbRU0 z5pMg|8}RXZdE4zDft!2TjLpnkRKc3&OL`luKM%YRjiCz?D*QDvC2CIJtER3{& zNH?{35u+w0&n9e@B0H{`D`N|~Ex%FHj#MOv48w1Xkmmj08OpDT82g+hQdJ|gb~~mG zHJ;H7K@>Dj6y@+KB9`Tdn8M~BArY5+_+&detqPI7x3CkUA0Cb)cMK>sj)60HhWaG( z;7tCU>kiO&9};|Vnekk;cL3GQX70_(pGFV&6|izIDHDbgW-Z*%tM6B)tZI-?Yg!wI z@s*u)!dC6XlD5hmi{u(t26${rynd_u+Ai^hZIY?8hG+U6PIy-uhb*nObbELcmLnL0L_hgqk{3lMLBpR>cJqBrec-pPMavd7y@Q?nI2l01Id@N1`$#C)@IV=_l46+i#vM+50aD6ImPNdsi*wRIEcj)6J>)NYuJXBMn2 zr!mw$nP+BichieZH&}#yzi8@cGbO1#P{8_5em`99((Jjnyw+x9#wn=&+ECA_4LCA+ ziwB1=p0CajwC7+IQSwyGkQZ|@7jjk1>14Vb5<9o?SViMj%9LB{=6dG2FEOrp&i=7R zqU<8D1p75(yAa~MUenvNQ%7S2gD#3|@ND~X#zMv7g_9dHqQpzhB73BaFOx0k6!Ol2 z+SIU-(oBsj!DP&MMg7ac38=o4nEpINKOw2pzTiwL>1$AI)A5*<$ z8{L^UItHX|>Na#(i7FNBy-KW585j>Cid>)D%=hLBzoCM9yx>h04v%*gVcU8>eoI(M zT2VOjCK*eYnNZB(YAhttU&L;ty{EU>9&=P(F20xNa2b8lII|;Jl(MN*sJx6v*RlJ? zSgLNA?;l=zst>J$>?vP+j-X zwO7g}M#x1z;Yu4btws--_VzIvRj*xWt&xFIQDmX)tgHy1p;d-ONeo$Cs6D27MhtGd zzRi5Vz34%RV$oMEG!yem60~#OTa5j_rLo}UTN3%vneFgEdTBSrna8rwbz|8G(t)h7 zN_5?SUU$NU@q2Zvo*m?zeN*o=-y_9apf53I0R!YP)C-UT z>pvDP4hP%etV)2NR|2!)Z5m{cPlI(fPl)T6)kH-4UWopd*sL+>L?T~6gza5p-N35R z{e@ah3Ar7;r3JT`pV<>3YUojDhik%sMPT>6jeW<0_Ep2`7uJ!|T5`i5KVPDV({w`N z2bnJl$2C;H%jg%{xZ$}18?YXePDRz<(q2o?b{02T4?J>tI^X@7KFpWFJtnZYph6~k zstpkw%awFdw_zrFx4=GL%B-_&&|Y*v7tyKZ9c;{VaCl!ypTXf}Okmiv_iuW`(m2zd zcfMI2KAn?k$E1U@DY1Di{k&ImzP0)1-Fg{fXf*zS1mBd%?X~;vc7xLZdTNWp$WgDH zt}=_}5(t~J6cAFClBYYXlIfui|5+dyqUIC%KOMiBIs#kTZ&p{(wdm9xo$sU*US%?$rLia(icSqw_I{~m?qfToiyLu2N!Ip_eC>NaN~BZZDQ#5ehNVEJ z&FGwp?f&~_nY+dtyMrwr6Xrf#mvsAjR-bA-l%`bgYprb-$Uq$yVa|2gY`*wsdL}n zvM)Sb>U!V&`rDhQ$0x4d+v@eDo(eB1WJr~vl2*Hw9r|jcRh)_YccLYp(FDX}6E(Wz zUgmhW=$iJ$0OzKbC;s?0fVFUxe(z+^0d82-!_ZlMPBU zv+1L}Zv|oI6j8^~b0Z-`6Cv6>F45Sq2>PwLDc2@OW}0)%%vTfjmGr|cWtK5XX;S1e zt#JhMtl1yE?tElK+1K4u{R(5S{!=G;FwgQ?|70&Qe}58pQ_si_|JRf@_&OCV-Zq0* zaqVU5-Ab}N^I8Gn$kChzS(xj(Cv4rIEhPPLS!aRlr#D#^3QNB2sZ-{+tTHjWFyHj9 z2-p}fr6XrXhYt(}p5r;c5Pp6ULizHU@Zi7Y(3vEH&3Av06Snl6{$R_h*~l_9FnNcdopK6Pc(yt1 z`_$g*QpZ$3Vz-L)qd@}Rq$+GfCUC`4;%oit3=M2ePuo_@o$Gf`)fWIF|GTFe=bZrn z(>TZt?#n95W<8PPp|h5^5-q$!xIUOqUtGU5K94*$wK1U2P|@RwKFa2gzka7)eIfhC z&%EYSxcs;H8%y$otctvvs(pjX;jT5kdgB3Et{%mNUE#e_*!#HP+ha;P_I0(>agkhQ2+gMLp!%%ewT^i_b^kA6?l`YxoH` zt^1jZu8gX*VGV+sN;lAVI4hoHeh~(?wnC6R0Og9pbYTHXNW}d4V=U z=XqI=1yC)s6zf5*lUBN^+LFHeni!u)Y)CKN*{2F*44R>0mXoX{A#k$Mk&YY|I8LnF z)%BaqsDUtC2iu#8mSnrmI-jYv)yjeNt}AH=NS)^zN_wYmX|DFs&dC6#;1DC&aPLm8 z>Eqp4S7E(B`>7qdYYgE($yzOEF?g8U%*Qhntg!dCarbgr3Cd%4kAhRQFsUc%eSIso z+_~;%M|8q}cEQ?)o3sy0vL;jp?7kIs^Nn>rTwSZa0gkEb8>pmP5xTZBNX*N55D^=Z zFW1)Amf%gES9=fi=$Sg_Y9aehuD>*vzt!+i4qXze z!8Z|rhlj;-u}H5zaw`1{l~7fs+nb9JTR)`PGGp|`Kbt_sBsk@A2^GV%mMlro0E50Y z8JLb3_jDGM;Tyczqd3pmTGKK-sd2cbdcgm-V=5VSozviCwJrbHMm5E~Rhm#-w;IF1 z`i)=I;aXdc5P`vdgG_`4QoqEUMXjaz_2HZkPc3B_ahjc6dZ+ukvSS*j)b%xVUTbl{ z5P4O)tMdW4|3>OIW)@`nKRrE$>!o2sW0?|Gswe9TO*FdS1IdS*DDsO$caI)6L`5m2 zICZVWvf>WA94dWwm4*Y!%47Wc42BsO=fPrF)rl`3TlRI{6w#Ovxbu=Ra^1~sCCrU` z`j|P>S4Vy6@kv2#h!*ifBh~FSKDo$p&%H|_Q;a zJq$n;&1a3=RBV28K#KbEH;tqAwK*Wfm8t*b<6+p61Qk+Mt((9)O`H%p)WaIRRT8Rw z+99I7X4(2n2E>T(Y>qogw=sRs8a=eu+8KPGIj>8b`Cw(&rq22N3aU)2jn(G8Ayjvu z_2piL-p_wHff9NqMj7k8mRc+D_jAiyOa3`9{3UFxrBII-%OK z{2|etwAE`@wnKud&SY=>+M~i1n=RgML3% z(|ZSV0y9_0!sPGj;=XkW3CGk;6IQ`>s;UNxk`L1RhInqH(v>@mjXDRyqusN z!gV#=>6WVMqfJgei|5hpm9??k&>A~^c~wOM(JjSIwzk1{94BC8c6EDnYwcz)WGK%}7=jJ1dg(^xx4%9*PZ8Kl)*-Nw)Ig?J6)U~L zx?VlnGg#v@5I_njV1Guu94-y2<3L&q>LxUEP0#Y%RfIIf8Xien%RFBy;1}q4cOd*T zs`g*5(pu;EIql!1z5C}c0Ss-eq>qh^m9R)ONSMR<_MPpDn0#ldcX0y~(sm5NYU#S9 z=~c5y^cG{eNvzA3*4ZQDdzaV^<#*vmIB9@LkTweL#>S{QhGPvel#kP49I~#s{jjTi z^|fn16WPOb5!8%HEK}OjkPjs?`^qg8*01DF&@k@xDlal&fWi0(W3Jz;lb%XlrHqe} zC`X3RO=T~FiGz)r?0I)EFk|>YRjj)%s3T0{`4cWc&w0c&+4o5nK4=l*OXPzpr4iWzdo(%>hl6uYW7$ZpW7G=NTH+eU3->m~a5(kI_W=d655v-mjL`!FgBEBIF* z!}GHFNk}rvE1ZwrsLfF}%kY-gYE5K4fMSm7)6VI0$c^zg8vLnCyl7ZHky;rGK2$>C zhz6QwPf}_75#Voy-9Ll>#syHXFo1g5UrhPg2UZ#08|xeOII-}62#{$%3JrYve3k;@^gTQMSKE$NweIAkIJ*0GTWxOX2uvQ*?~l_5a4>Vy zC5x^xX{f8h!*Ij*bHW5KZYak%_uO-g58Vr~DICc0vvg8)k*+yYF=60jfoLt@AJLW{ zvNEg3H+Y*C}MB*cqQf_uHf3=B;cw5io%GCKl!Lg7WKUH4H(fpx6cf&o& z4#%q{97tO{uXn>9*kS!G~36282*{O;&=c*Vkw{k?u@ScxW*Eq6aIeV6Eg!A*{q5QKoG>2}KTpje~{!nRNFG_a+q1*Pqp3oK}Ae{Xi?(?4t5&{;=-UPDhkkK)?4BdJJh zzAWr}@-0FXmNH{lw+4Ha%#gC{LzMuV%qQTQci?H%%cDZ*ad==d^BD}_v&3ws;z!oD zx#^1H5&eZLGI6H2w32Z5_084AK}dK|D&8>M>%BVsDi+{?t{Ku*9|!hS%ZiLs#W##* zOC|a@U0G-^`BT$8h!c+|M{1zUg2;Iv9NK5ncrD_OC8l{|Pj{V(zIDffvIq~a8=aeO z$&;r%{F}+qN@PpqQmn8&W9VC)sg9!Wwsm0>%ei~Mx++GW-jPB)%nRCe;c`}&%XY*%S9RT2ah{8q&HJr2(HyN{tfHC}RM?Z&vAV|$>uD!& zo8Zn4gzLR%+77TMne`*{UA*vWPl3v*R&+1{B;O12!<>%1sYzF+)VG)D3BRiO!Nz}T z<*B^2A>@xQY4i=*qKE zAx%|-wnwDLUb9daYc_*rVYs8k#9JhWsZR;>s`t4Y>Yc$^$4`yNhdycJ9BqtGC3+ z(qBA%aYP$**=zCSmzq_9e%biS#9QbrOVvJHh3h8|v)D1uHkYhSQQ=rheRd`fdx@ng zojWNdX|tgf8|FY-S8tX6&7+pK|G#5v*I3Kti(%)}whvFD(xAL|xMB56Hp} z-Rs=5G1PpHk=^dp)+r|4zD5jQ>Jj(JGd6)?`lQc={S_EZ7VBARhh_7Jclao5(GxMd zZWJuitUu1OaW^&TWeTG%Jaf8LgWucAL9BM;{5&9rX;)EQhl;AnEX)$NMQF|MelZ|b z{OmLDL2I)(r(%7#RECb*C+1z0FA(=@k!B@mmNilv_YJT3+Kx>aq5MOr$sGF%t@0 zGcx=q+(SW(NV&mQxOW0zB88db85gs@#L1s*{6rKs$cOT>Gzc>f-~cGzG$fPr4RbJZ zGR^)Ls=j*4#KEtsYB@4I=?07L&2r4D57*S*vR5-U1ox#5zZv~ESgWVXZM|Z zx=P7i%3_!E^uWGKcpej)$CoRmM#x+@YgKzIo^5vv>Bx5^}V|L{bZI%C_`1D9D!>0 zy4lu6ixxB7tP@EWbFtynSA2ffBo-kb(fIhRsWFReU@oyXSF0d`9bgH4gmN>tdM$C+ zK{wc%&CZ-m~#Kr`XM6?$qIN2<@zoL^5x1C;dir4aFgyGt!e56DWZn&Qwv+ zx&o*oL#WaLu~Rj%U&No#ar789$;c6T?Yb7OX=tWiws>6mOrX7GC!ViubVq2|7P46@L=CKAm69IA^ahmAbWv01u0ih+cKeHyYSni>I0_pE>C2Ccgy=fh zkA?;RL-%{Pp)(m=R5qcF$(ug{QOJ(C#w9wLV`aS&rX1$_)4~{=Gx2=l{N(_CX20BP zX^J-<{#e_`>w3t26Xp1z!qt#}wjFguQ2etwUt_royBj+5ENu=8kbye-67FhpFLgAfRVl=TqiV2WMq~ zP@~0Sj*Lgs(r+#wNppv6cH$f2j+W)ZY{jEfuC z(|@FJZ{**7F2-1Nl;7ab_wUThAMQ_HDQ)Ji882l?78ikqSWC=)GCs;k|1&SOnEnUb z@8PxUt$?i=%E3nBEtJkBsWBb*)VZForX)^+ENM}$hLXqBNHRD*JE+k7u~*|U;+FOP z?3w%|1uSx_4lifGl!wvOVbFbfVmmuhPI=eVb-buB;ALLkwS=teD93Tvnw-i4lM3Pf zN6lDWrYC}Rznphmw~7WUauK5tS$6ho(ungh3_uC`y?`tS`G{ou7@8Y9HErIklo^F4sv$Az>!}b-2ByzhcC_8Dm+xqsaNP{?UGs*Kz5Yw$a*o zMU39UCG8^DEaETD2WpU8@U^s9-%Yj~fWpiRNRlyUv=`}l?*>q;aN9A|EGNPk)OHv} zrTFjBvpcxla$+$mRzy?!>!j~>G*iJ{;dqijJHh?POm+F7VPd$|@zpq2v8%fN^3AvP z3@<1-7V1Q^w?`{Hvo{;gq>nciol-7SYt*SYbuiz|VPybbvG{(0(=xCWVwh_Z*56Y& z@Ch)vFR1=KBz79p^3IU46^t0sMrtzr&KKP;f+42~nzbYz`!|6<@Op-fjH4SH4Z8Ao zNG;1+-Nca?!gD%_6J|TC;+s3N>wN-tj9A}Qb zW!Quo`Wx%QrqGRG(*4FyJ&k9*1#2LJvP}Lw2aU-YBub!@IrNH7w-$s?ME#&K`}osn zYyk0nJuvYOg&Q$>8rD{HdjZYwR66S&-;(DoXLZgW!~w))t9uV%KyLlMXu)wk7u$^2hU| z$EJM-dqv#N-;AwUE9XP;F(Btoye0znOAFwOcxX)q_Cio(I)m%IQQTVC$E7hcb^XB_iMnH7KB_WojkcY5;2quXvZfiW=45QujHmt6K6WX>N5k30bA7 zit5eENzu5+;IA7ReT5g->&p#Se{QKH&5~sx0$evym7tia{pd;HA8`bHk+1Vz!D31C z{)biX#4}vq^`*kf<`)1XlOZu~%bWKMXy3IO6$YfDN&}3FNVmuofw-W|`FoFCkGcn#(T!_o-KF8Hyh=8u42qAf z1?;$FWf6E8;=F~n0pSmKs+fUo?`w{)t$Fc{VZ}wUR*f1>tC!fG`D;K6U@`rcR0>a) zRc>q#1SROVa?gR3_0~gr&xlFIGy2vLYQ;w06Gu~j(DaHuO!{y_@k+2h9A}!jjwu60 zaA~u?k$$h8P-rTT)HDCyiX*C#wbJ>xsNL~#WMV6WhWEs%-1mwq|B?eXpZSo+(u4Sa zkPt75oT{N}p+Q|tGoAYp^IT$lY|)i(-56rhm(`aF!cO1Whfw%}g08SydQAtk$~V-) zWYUybdWioRz$ox<^1|M>T)Bs|O9rw?Ur#|c7BKZv{V0bPDIzu6=S zG#l)eTNE4T#t|dfjMbdStvD=QVk5vNcv`s9#L`7YcUsKBww7^o z(%l_)1O3&5BI&(1SP)Q&cc-}O(#HUj=K3^*VR=1kP5r9?f*`G@ zrHd1>O774+CZo%RC9);t` zJ&56crp57s$6SYd%Dz8$l-%9&b4#6ke;f*~o1c?p9wnePuS$RM;8IoHa68p^wxwyl zuPl($aP38~J`zdfkLlFjkcK3o#;=QEQzn4k zvSXhX7N4&l&s)y;b;8tF(C)dr_F7Zfe$K3Hj_D{#3-7ti+D3cnHOt?8Y;RFR?#27^ zgA2skhhVZ}d{^XKp({Rw$bsCpz?#)$lM*WU@UnXo$!fU#o&zTiQVR#ac&LJ7lv%kA zDbw*C_LYPMo0&|Hz5HVZ>{!hL)#YeLW#hXR%i)4i+p3DS@;zRibA_NfW=QGcwHUmt z-BzmloN{rKIp47Nw-&(Ol7yw%vM*%k-kXwy5&2NC%Bgb|w`->Lm<6sLp9 zkhPgm{0}c4LXCB>zC>y}hYBcg*JRPBPPAVy0ovB{K=5_;P;q^H1jrw&0ow`z{z`ub zzY8-R4vMVvugXE^c{dK0l?eePG8z0u{RXiUTpM`U{K*`ph7XeS6md40(z)fO?*_wg|L?eKdv!mD z9an$8+9>tdD9GkCNkJ{mo%-~!MZTCebat0A;?@F@Ag+sE*6-B&1YaG^vPUV7%oJUV zZtSmm45+(HGIWi)b?QEmHRh>nvQ^FoKWctBe3R;4E@%_vvkq&cI03vZsyVASj4@V8 zsNf3SgP7Y6=EPG8IVB@{p$YVtbjMApuSR{>;Au0&jWYZbE$)*($v!{hD|BmZFg92{ z?!==M`tK=iqWLP#XY1ceH5ZNsK2An9qgHNCN7s`k1<5L&^87QlS=-?hb-bb}(^Wf7 zw&l*sNGiwcasRr8vATj6p|PG9vk!kXPd6pkF~U0Gvl6%EW&^h5Z@G*hV4;HqkRfbv zXRDk`XnwW1HM-TA;F7cUGC+aJ;A6>IIOKzPl!Q?|OMZUJK*2cqEhmE#!0hWStRQD` zz0*d-xHG+JUiOV_`O+1;75RQvN)DNq&t8eE>R*n0$!v7@GP8%hRx~?(`t!90H5mV-S3d;vBnXid)59O_hBWp`#zR`pgcS#aMF!qS3`FZQ7rEDYm?0mDK&Ep5B-`1<%;l~>^_#_>%C zWx`&|yM`?0mJMt5^I2(v6x?NCkE=Wrd6b6lr0E?$*3FYay9@661fZ^aqof>I0VE!| z&UKK4)@JT6;^ahX6tKt6UK$X!oUH6Z9vDQjnss!Y;WUuN5(~V7>@703`nMARy*oU4 zVNQ$(W9z`rzxWbqJu-JjBxj}%o?;b5c#y0@I%zLUtu;5^(M2Q#ELQohGdsjeeuwlL9#ETq?4_o zhu!JsPe3fHoLt^*1F(YW3`LrymA@^~H#8k_5F!lkSI`Oj0r-uq_^S#KHt1_{t)Db zHrr8w!hTJ+x*!pftEwY~34u{Hdf?Raopnf2+0yb@Qf#CZ1iUHkHeMwyIHET-r=Yf{!wVi0e2ePrgz(P+y`msHn0`#Jg>ts7Q%qAS4XDFTl@l?$h!$&qPSq z@9F{qCidLoUQ}~KR087Yxpx(2I_@(T@X2jaBt*UF(QH(M`~ez>nvQ616ei;wz6%$kPPlx6dwPAzTr&pzf{#F`GYt~4`6rH%*Q z#BZ1GWYa4_iQMXBbyFrjSEqjA`E7*e&#D&JP2@W-KS_cmU(( zIw-{kdsF25(H=V10TyQH`i*v;5hP8z3-zmod3R&>Lp5udLB54zQND+DJA>;D&OQeX z#6yoI1>;9)n$06Q)g{7Ejr-@*rx-?Z)I5|J3^nflsK z)T)7RU`+3=*(`H_drQ0YzYi0Qyk&+0wfu|@6_M1&p} z3aeqr@bQIs{!YXBskkU?_TZ1*{D+VWR5jU#Jll-U*ob?Zl6%Q>FE^IIzD1g9Lwh@s z2V%TtMK1_R)gIx6rbD4`S=Q;Y*T$K(o7@!oltpKrHI|`iI=erN!K&81y`PWP?K1;# z$aEQSb56NpdvX0HIbG2nt*QJ{1G_)>mLZ7^%PnLHqir|)=Fn8>!^Te?RU+s|QF0`X zH~HDjc$iV+V!3*%3qi>7&;7p5CmR0fAi$+6M$8Wme~{>O5A_X%dt`Rj9PZ36e_u92 zgn$>@f?XITK?CYQLS7bKo8sl$9lWL&Fgqi&rZBosB+*28{LNB7(&7mG@4PW$<74V@ zq8Z1xaW294QwAicOdwegMyVO?8z?a4-Kwf-yJT7qv*!sgUv7FOJXHz{n)di|!@zAH zjQ@^UjRNfK>yrBU#rHybOs=^d&?`Lk4yAd3XHaay6A&{m0uz{P?!g= zIvWu^T{KyF?EEENgHQA$Ez7RWNOM3Y5$&tGwR3CPI?Tc1C?FwZ*KhnR)hpJ@T` zpnCORs3q*kL{1mP)9BHzF+?KGXVaF~f1(8jzo0uAWy5z9wBM4*q9L45F8rBlUAx|+ zaU|hCRD(cD2A8O)=G0iubUcmoKm=lg^>a`!Y#UgMBQ76@k7bmq9rZToM2?sfU1K;! ze$YE;f0x%uPh-M)N^~rsjMrJwc#j6Jmb@qRT0C3ui5LYf-{DW*x!=Wc;11Zf@Rv@J zf?F+M%4tm^mbmklC)KK}!ZY@XanFTIk?YGBx8#E35wi<+_ZbpXj%^N)F%jYRl`|A6!V!YczJ!y-qQC&BVl>qc|BNvsR6Js7#TK5yh)@3aAJmw1cSvKo~JI3Y@zqW z&qNl3xpvb6KLxVMjYWGi`3U}G@B|3afR|N4`Vw+x07hrZOm^3Xo_C^_fcB0Mm{YY~ zB0U-=^|lYg3DN~0cq}26V%$kh+#2E?sbU7ZTa=i%_dh;j67n3ESf<2{+peReP-UB} z03a#YNHe`~=k+JxYj(?HrafBBskW4+A?-Q?B``_ib$1782XjR?o;Y{$IR~t{_XlcU?yuX z6<%ae=#>Lz+sE(G?e~SJQ1?7Li2M2w9eAK=md*Sp@s@V#@3gGO?!R=_R*zTAqB(3s zE1bNWK+#m=603&KuU&CLn8L2QmCLWjVkQN%8R2-nd(m^*l=npiKH;8AV?vc-B+{$B z!DT$Z$cp`o6J>V>>M4HM+XZ35Z5GDY=DxJ?=1WJKnXJ5Yb?6cJzErB$>GsqF>&eWY zFy%PwK3CkdfXi^!i44gQJT_-_Um0V$T(mh6qo+j@(N1Qo?KOqy@w{Vu+Bg8c<`|yu z`k{}`rRF@nI+!hAv_qRM8z2|t4yqN%W16HCTd^G<%tFNx3GZrte?a8PuJ+Qx%cDND zi3KI3_qetF<_F6yJw{_^0dALA2d4SsPHK0ZshBaKH4YaVo~hT$I+nv79@|$rT?nHU zW!d_u*30O{Oi;p}&197)*_vs}gE>jfR8i&8qM{}BqC=fT7sSVBH(BWUj|ecGfR9Q1 z@N_Znsqi6YD5&VyM=10{@2u0n3a7M%f%4X>>gFu8Rmldvb@Lyl(fTpPAT(@t}iu1e{e) z+tI`3JGD>hm!Z?mp^=4&+2WV%Y}O&O&#W3n$DcLNd6F0WksZ5*?xV1s)Pi6FUguGx zF^|}@LE@^{-JW}NRO++IQ_ON7Enj(@-S`Fbn9SXLbswIcI)qw_r|pvMEmt#;NUkqm zX$wx%HmqC0kB;RP&3d)SB^%<4;UXqw-VX}uo9Z}Z{GAtpzonkWMcrgVudC!e9g6Mz}kjlZE1FLtjCn0EY- zqf`5(4sDRG{~E2*BwKX=1>>UzBeKuWXhg|v)vn2|*fa#|BNSQO9I1Y8 zZLa)w^8k#qYc7KrSC_<ZAfM1WsH*UEK)S^wb`7@?|QbZco(GYOTCZd9Lu_Ugnbryta)r5Z|Vx`K9|0&BklC2&b-PmZ<-wIb#>OFlk>r`M={u|BN%xZD-6mt-;?)UJJbqD_w}`#&;lPXDF$ z{6~f@Nb|C)vTBECHYbDdC0#{qbSmxNWLT!G>FDcGoI##7Ab(8xdEqgTYy75kg6N@H z*e3MLyZvas*gSx=ft%y?iOc&JjLEC*UO)bi)>)9b64+tpVkt7`PF(+SfOMabTfcyS-tE*_O*GpEmsL%{^O!kN6S1l9%ps)?Edc774Z(RV0?FONdXqEzh#u8YC!)yuBIvWNZoSt@_kiO*Ng0wzm;rK z{H!P^f}UM^j39t4Irbzi)V>cgz*^(?lUlQg)LvBya;o~FOk7|%q z0L2;zz1(-hgKcX!0d0C^?f1N5#YK}XlP8X0Zv02?4y#NYh?sv85ZS={dlJ*<9msWj zoIm-PpPu3b@*_UaN^Y*_Svs5W99iT7Yc!foa-WeULq(kQT*gb8fi9UfOKN-HiKxc8SUE?98Sgna`&A zqqTrj)YN_X2vxU((A-#+COEmD@+6Bhr8=>0vaO10;2B9Y*%U1l&fqx}|v1yt4?^ z0Y%i|D!252#iCdtOF13d<_y#*uog-m?zLZbGm6S_)3nMCYXsp=y-71Mu}SJ?qx>GS+KkZ_&4aipC7><*o8Tu`0V zH`_!gH|5a)g;M1_)Km6ryCKzb@zypm3cxMKViZMJq61)b#ChNBu2Rt*Hp?-TyRkl;#qG3B9?t0EW=GD)8 zJX$f`me}@=S!7~W%=%04nV_s6`#Wp4KwuOGkC2==%`ENZY+i4tnIx=a;kQ&#?ZlJn z8FP+B^fq{_SM(f$z_mKECilgLBE9St@Kyo4>(O0udjgc<5GIS;F2f(+whg=kri-m$ z0XPta015fiZdaAvXX+xdGT`7>1HA_Ll#@k;7Ijjdqss5DUc7x@2#VLA=}cuE zFvN@KpEI(+C*8jcn1z?o0f5QMb^_2s^k31>Cr{_D&B|MFcbQ(FZcR^|7SB5!^zP_< zKD>=v0F6~x)|KCf=)$)vpra}r687c7Uw=ISnmBYU1?I%Q+ov&(CPTnK>OOXhgh)}3=T|Qc)&w(HV-rmCdO*Z-P7Nuy{y}Z z-~LR)@veqow@x9M0ceB~0f~+p&Qzos`7mI~8?QhvCLFNvP37vUl0G`<<=tW9Mil}7 zHQ>lLi}c@fTqzD<1y*_VZrgha>aV;rqfQ~mS78V|BSnG>4bYz z64%tA_2eO2C#0*x<6$CLbH}DrAP8M^IQd(S^t3nF^1gq^pQ9GaHy}nDOst{cH4cEh zRJETCm$D;Y2oYjd`R%Vik5BC8FoXFl5L|45lXnoDh%dh%bPP67b^|51)-F=!y)sc< z86ic!4Ln0NLA=PjT?1^DtDVp(?xCRdeg{O0F9NzphhQH3k0+^M9r^4Iw$dzOC((k%h=x<_{M*zZr|~C(zlHO4kL)kRL@(; zv~{Lj^~g9@W@jpJYreGALGBx{&CEOL2Cnl(>AP-PBF{hk%`OU(Invk?Q;Y=1B;w=0 z+T${SakTp5$BF-JttiOR*k5bE;$!AxxTsGc`q}QT&zD&@N1RKi0IvkbaFhIm31Ax> z7H>axP4V-e{|UC&bBHk9qdg@Bh-+slNsrq^c4LcCKmH?Md_Bta zU+8IgE2jV7UHS;Vtk~Mjb-t;80&sV{=b?mo4m54zc z0%$cd7frPn-x5ORV2IT~>oY6lbbf+g<9Y$<^R1>c^TiGc{qOx!C~r2%IS^uEQf%~7 z;GUN=LU4g@WOes;GN<9mb~5C!hWyfSy8K}<<8rWm+cJ=h1Y&2PBRXE*lakl|h9-2DFa04N_}#{nr8&L*+13%oa-l&dPWqT4mqQHn+K%zA1kZ7C1! z!+R5Xf(;>REvDnO?psqnZ}D~9sqrUR!t9T7GeToKgk@qE_X!ER7XdtoN_@MPtp|4kFvA-01D(`$i@0F8O~;%I}WTgQb*;-qQrGmWj) z1xNsHEI^i-nTBCt8`{e2_HvuQ>{M?XC)z=Xf&9(K7H{`_<W&=rlEMT`I> z{(niJH~gC-m0Jn)c#7SnEz{U{6YSL3o{yPl()1J_KYjV~vpzx|;+{1sj59c0hUL9~ z-G!!}HBUInGVuncPz_#WQRzU$ohQ;iM+|87Lv$Wz0n!YPrje?)=-sFX4lA zu$)}lM=QhOzOTAISs*dD2$5bKTsp>*|LF1nlAB-bGNBtO&G734r$8|rr5IgK2mv{% zLa2|$^%?H3@}R5CSJ~$xEOO^&15v8qZIVDwgc=I^4k8`6)77ZweS}^=xYC3I zL@4m3DhneVsH_4Zp%f(wS*F7#vur5u2fnq8A0QEYxqr}KX523W4o4~NhhiS{fBbrt z7hV~^cf^bfv$}X)mIQc>^V@%libI;PQ_y~Sc5gfiP*jY3XVt-!e7CcMP^{rqekZuA z2Gudub-Q7kP>G!FptGcwU}PCRCD1M9PZA$_mzzXsC%RzMO3m z&pMBd6ekN5j7XLh4=VFP$Qr?kF!$Y37khct8*jzfRAXn%lX~XHW5>P|KkCkP$$OII zx)Xomp0a+zJA05~XO+X^*j3!LWrz2?Kx4+qIQG3`j-ORPxF|EaRupA$B)Iq-)~dMW zhg24yH-F1>P6VRxGB5flK5CIpIoQ`o?{99_+;#`o>kK|$wT{1A{{#Iwb~Wm7Sg3-ieAklMItXgTcok) zeiRWK-%eyW3q|Odf}mg!h`gX+#9`l~$Pdc-#OF8f8FXZtW+X1xjnLw+f?MFF&XJwe zwm+5oX7E&gemGkk;hHc*a&dCA&NfnLhJVw}B$7-`#!oV0uK@W#0-Sj~thn8rrk59* zH<)KJKs1(@I-^?NH8=%ZmF$J%xAzi1+M=lE<3h{3yS_UwFj%Wbs#dy%>#r77r`~mN zN5_6xjt|Ov3A4oT{zvZFj=$B2dtXU?4!hG%j7lptnY}OxK6V3 z3`rJ-rgBLY)m#gUxEJ~sH=auNkjbC2IL^!0>@G;)jgZOX<6)qPszYl~ct=*<`Lvgb z@`%wWm`=NX%(GaVyPXw{Y;i@DW|%ymuO2P0-FPE9VDR9ZC31d2al;FWM&?#I5ol=G zNR{T;XG{T#VU2Gm^tkmusNFUQ7Dbk%*(W?Ql%z)yZrf6`1RT9!$3v5z#UWP%O7?FSMO5HAuWHb1e2Gm)b+a7O&~1=~6rE zbEt^<{&^PuVb``6r`wG~7f?uCN^QOQ8sFG~yK+#eQ6j5XllgkQf+c?bz;S!*iH66- zR@mmiho-9&)gm_`YPo%qfaSfe$H-oHQP`wlsw82OvnN*#P}#^~;F0mdL2Rsz!(zTR zZs0~bjqtnYTm1=a>Q}T1o%dIc!k!(Z zD)4nh(=)7yF|Cgy<8zR4R$0aT;I%pk%PBYPrU43*$4xWo))6E&&b@7o?yfI~{Eo`3 zb}Y}jPdr;8Nyb$yc9|zs+RvrJB&7SVY#zYiz?wf6c-4PWN}|+So%{$DOf(o=zGNtL z{*BsTFH(d;jf-TaLf7QYukeKKD>90;$eADX7e1j5uC90BsSH~U3*K$w_%4S{A93EX z)6`k=fuT!;F}rhyyAE#@7OCwC74bu!K?R%{592hNk){=Ho75+#s@f-Q*y{}nA08yB z9J=M%Ns!hQsJ*JmRa_qs2)+8K;c>|A@=;Gzi^6&Hs^(fOC0soIW{++iSKoGoc_iy5 zP48O4sMkpc)Jc(d$9iD8h*|T>q}0pTX$v@)IeK;`p7iK)b0UxhLe_G?LZ>?sid6F@ z-@4+>Q-2$~ikUPXRJetBQD)wf0uLp2_@NMHv+lLgS({-YnnLYL{vM~P;H)qLZnSj_ z&uKN|<*0;8tC)7FM2X6R>6<#DLEi%9CsCcnVaL@LXqDfpx@lEn>JqABHE^bg+3i+> z9n`1P>9;+5|9gAJfb)BZPxV%3 zYmtp|qD~T@vQ73B$nGvBQ}-!-R4Q)Nrctc_Y-67(X!f!8L1ss*)^eP?EB>l5?R(fC z<^2z(nOi=YEe;5JdyGLW)OvousQ{;Np~tZ~7{Bl(&GeSWNz?Ag8YjtRg@%#7wlX zG}IDJFPrjZd_gB0%y-5lM+f~_qLZ$cj$cLxh4d3JeDu*Q+=bTpcc zXj*Ci_~zJ*V(Q{pFtCDm(GiLtvwcz-$6n6!dN@b3k^Vdl}|cJnJ-jGCmKMF`K!?@wPnrkpPyN_>eGbcTqBDdUB+` zd(l*lVdRdxA|{QJLvmb%pjpI0V7Bw~uFG`K`MB9mb~vP3{SPqEA0Z3e^oZ0F)*MTB z%G_=TMWY^KS?){T();rlR-F8J?A!Etas??FPh_7K2u#ilvGs}#Hy;^Qo&&s%;oLSy z%|*KA_SkyQWo@MqfAQ^5S4-U?FvZgw6nS&aZZh_TxQr+4G=f2@I%rxU*$|UM)>QkU z?(E(LPo|J@Dx3TXrYiV@Sfb+hP;8TEO@j`>9@QHqnp(l*_H0*1>eB&oP zwvt(!K1WvLj_KOArfTe*H|%8=n=)lf1q@#%1OhI!W4#}F4wO-b%{qDNqL`6Dpv+ELm!b+WYk&b~>%accoJX_|6# ztQb+H7i_3k8%%H_{WczdP#we@SV<8uG!S?Wpw9YY>yX&FEl!(+t$LKa(s&m3<-u&v zkW#+3eKH15^}8L^x|$`&*+&Nj3|K;!-g*Gb1D$(CPFXAu3-&L|xmyd`>OB@VUKMmZ zH|Au&7xCYjsT?{v4~DbdEpF0W)wb_Fy3Q&6gnUZVQ@~|5tJ-FtTr+wQgn82BK&DbO zwclFHTsH4-UvxvSJHZr&y3MI;lDU#6K%@uv8#|fxZqVo3Azl)I=iFzR_rTqbkMJckVV(P!tVVEc)5+uf5g!a%sj52ApK` zWP$dB()E>4qhq>*`Sh@y)dZ7;j_Z}wwMe`#0h5^e!Puh8-IIqOV)3yMdb*n0dd((`Y)4^jx6(zfL#L6tu^5cc$_EV{1mOr}@81oe$kH}{ zQnMIEb>e^>OcZ4ftC(JtpeWKR7jF+ESn=eSSnEi&5k`*RU8%?|c0Bzts*|an6~xc5 zSKQ~eQuAVP_c$BXlge}^)l9j;uTfy+vc$S=BDIYxg#7iZ(ezTf>%%DB7%^*ynjPZx z?+yO1FxNhSgXv=E!kx|6{ijg{-a(R+-J|AFQ_lOa`ENXXHyL_fJ>{lYeZ zwoL1zeBo(+D%tN(N)bXSlZ<&#r;Q&X&&QYNMQ>n&mPg?3UD4|Dg}?lkL?IBR33?$C zlj94$mxKF$Q=+^vYTIahu-~O*xB-8VuD#giZ4xjHPq?0fqe$2G(c~CIwRhaiGvmf-7+}dAT%^N1Q)31NhDzv5uaKrk7&Ur zzVH?eP&|0ceh?jFGWkKkle4{Iel?zO;K5@SZOhsk%hM0K<6O2UjeYcQ4a*r{p2F5- zc^}{xgrd9o0#_3|Bg3yR@}n@iF4 zgW(KdJkNH3OXJ*#r9YETn4WjjwE5xkU^z=gI7p00WHMVio{F3B#|oQf(>(rjr!h7W zd^lJ%HQ?grqzSjcR7qaeqR;T|e?oy>)Wu@MdFD zZrz4ho}}{9FlOmWEhwEr?hf&h>oMXD!|@$MMzN#b>cl&3bF&l=UUlWibiY|nd^Jl` z9OBNX8d}A;_||dhgC#zme{Y~-`E%yi-J&ETWX7|}*JAmI-%>rM$SMmsXZtzKhk@c$ z@e32Gt^WlkfCm)jA$#@Rym3^;jg(xRLnYC#+7vC8!shWdCt9;_r(T&(RA*)3=2dmaP8)fWXg>!@O2om|y?tE*@E6&%AIs~p|G9~8Ptb--n z_GKQ^6S~Qdhx)ZX4c~MH=^92S?{+*k?)UK9$)o4}^4cPrB2Snv=C@T^{BKsd(wj`J z#W}UeHj}S~y*}dVmC?7!4_aRsw&Y^9@p64{w;>c|)N+Q_2!{N1UAC=hhimk<>+Unt zkt(}2YSfb)NgPTU?r+mJbTa|N8n{fcasFYBZo-*w;$P;g*1j_!={k&AXJmmzdjZfi zO|WE%0Hs6RTVk4*-1U^VWHFqS^Nt zXod$5F*2^15qr)nlV$P{Wiq#CZ*n|^%uGlrT3&hFH%Tds9Aq*6?hkS^DRb214E3r- zbsvOF>dz;aZAISRFeKhtQNmw|!7nuj!Y&0^the2`1XgHBDTg@;Uww>^H zV0^|7!WK_lsb=qE-f?>1nUlZh^XQ%t+?so`dY9kPEN9U>M-uzp0)s4%42sbHcgndo z7D8vvld*Gp2h#Z$gf3jFM)1(Rw?3PN%mLOM6Zq^`{U~wRFxq7DcWe?yt%GrH9Hx{t z63AZmi|k+@-02>m=ZY{3)v`Zp+uvlk;Fm!^=o`Ya;1H9TZCW6I@)|Fw^6A1z+{*#4 znb4tLarN?a79jCs*uiB%SrVT!DS5Fr#23+oKvwbFK6?K*`{>EA>?O4ls1A%%*}Ey@ zs_5Ii+7;X|#C&qX`<*qm|H^ic#B{noVsZG8LcW(%h1F7Z)wOU8N3BLb-fe%6j@{$( zQ-*~C7=RJU{r8DAWTf>_{YK3BiWn@i;^M_n`Do|t7DQ}Bh%t)Jdjdrmgc zv)5It_d@GVqF5i#%w`SWv+O2PzlJ5dNnEazt;^M~kxIOGySIqKzd{&I!*G}9Kni|8!bT>uNV8fC?Ec^m8ApkR1O!Ay-*VaLuoBb~b+A}QrUk5R zu--+BYSo_SFcp(r0mrvgGZWT*XsV!v8k&5V!fU$Dfg-P0q_cv%S)_U%Qn=5}<9LvL zq_3RTUM3`-d415Y=e*D~P125@*Q<{m9ln`Ay35;slAT~rrct#OAsw|MWf_q*>xR0g z>O#wC+($Qia~kB?X6`aM#{pY`@GD8cD*SJg1do;GikpzXVj2c@c1BUYU47XVE%x1f zV$r3;WY>pjn`<^FXKSpH-brcxvry(L<5ioaC2SQiBduNyiCJiYJN^oniQfe`a_+4L zIJ=FNk?UlSS&G9iHjvm~eV@fApmt{o7><6;maPS0WIsZ`gn5MkMrN^vslP8yq#dA$ zp6blebw_KGP1!u*yr8H?o1)=|8(wi9>tt=^Z09?@w_oZL55EMU-xNqL-r!P4;F#!t z4?bC5@2KmBq=0h8kBZfjC(dU4-FCPwsEv~%T5UWUa7Hc*UgF2tP`OpwW?)dsQX$iS zgN1Rp_XjJcXYJAjei8gEfECm~*giWT4A-oSpV>s&!EJC7_Qi>-Xs)v{aQl*qTt|q< zr7(zu5g=+4n6_=^iDnY;I!M-5SWyIFx#0zqXC;%YV#Yi-q!le)AYVEvEoPS{kJy+? z^WvL=1?(tr^1(M07z9|jN38JFV`4DgP89MFMnPP#8SnH8r_>Z@`6H7-7m;OI_J+6no=HrjC#sY-vV| z=e?EEFX%H#E27yfAgty+!(G1&9_~##$=bQ@GR#CQ65{jyjTTz}9xbSmxQ=C2oNtm@ z@m8lM(n{;O-0Cx)aQnt|$9+w%9Ykp?pvd&@1k4GAFRmV-;^2n1r@q= zfV5PdpzO7g?DlD(U+r)pakEOq(mnUw6K8Ky-5c0o+6g1%mOSUa@L8V*ZtdGPn*pNT9DwNYWZ#WIapnGi@s1*q!NvBqS zWc)~>)Sehb1OM(o!&x7~<2d75p)!z8aJI776k$6sU@;}Nfa!^Q5NVZNjq$h5e#=Hv6s7Dv`TlzGv+jH5BVNs z3KUSZEE?t547p~4B#pjuW_ATWkDDk>EKmO)&qzg~s8rsMk=ZA4@b0wIo-~peaA6dWCPh%wB zl-H9|Jzc{Cy^>u$+9isCDLQl&94l9P0@~qm@I0$*O|tVO`)y@}c=p+I_9T@Co7tg` z8IbI1?AZ)8rjuJSZCZHXGg7}#GmnKxm|@>(!7fngc9Y5CSp{T+SWXAJz1IyhQj7@D zuIW05^VtX&;6R8noSBNxy!Ca14qIEOcY3i7qz8wq$aOrQoG>+v*r+>^9F1$=#5mg- z-|f%ST+V;516Q_EXSIB+kq=+Y%o&0+OAd5k_eU*MZyG?k;YjErcdCc>#Ot+hg<gDdMlqYCw5;qivrJ zsCp(lr$PkM@C%gt`#eMGPW6=}3*;YOAI0sIwrAnVjIFy_IXY){FoyPLdhwZXE{5e{%KHx&4cOrV(K^Aj!p=%t! z*TP9$c{Ay5)ee&>i$K(~;w| zFykP@0%tAq@v5VoxxDGxlkop5Kiw10OmGZA-s7G(+{#ta)NAChjU2AY(kq!M{Oa~< z|5SH;tDf@;JiCXsX#$t4x$GJZ4 z0Ffya{hI!LOyO-*GQ7w8uq#965czxW<*wGcht36fw(tY>%%x1l)T0Q>x{mi;n^n;o;$62gxC;xAx5{2`q}JoR^JVAQztG zQxJU*pSbf*P7(bcH3dbVYX>;O_`M(9~R9aVhHfo*tKoWf6zAT;QO>A8g^{PW8LOM@Y`*uSS~;$A_9N2wcznBu0{$}L65X+m z!-=0Eyqck5Oe9+pXO+#UFZtQYz5ggCKNi*PI8JSoy!0t@x_?DgZnocLHVUpVTFwf$ z;$J!4GXPnu9m8pmy;|;&vIXJ+14C~wxLuB7j+2wnIa}T~;uoXmSv5R+;d10ebVz!2 z0m;@?vzXAe1;&Lpf$a)Ha9=_ciq=kDPIMI{A9lrm?ND=kj_VF?u<3R~%cBmkx$ix8 zOBCqU`}m=}D-fPqN(W)BnqdPH9Wu(j+?A}cLZ?b2)JMBvGI$A9qBp$>fLPaq$wav* zfsiSIvdH|*G|GHG#Ig4ba5kG{!rZTkCz3UbMOe0C>(2)dx0SCqcw~4f&^tm9lF%++ zrLfsW?TGkc>&UD3QOk--4%&|v;qNc623_805Fbt#t)!!oGeolAixZEfQ+s3aKA8cV zA@X}KF8hXL%VqA6rb3k_T+Py8+09Ic(G91@Lx{(8L+|!s%@G?Zn8FH0rzQ&L`<+wG z8vM68)oih5u1FwL5<*fWDWxEAlj3Gvt~}`2EPDehTCv$6otdE1$7-rN#Ai6HgAEVC-!90nwq zddH1zR>umhbOp8Hn{sLGlTcZ`ZN2?rHTlC!ebmsvo4qgnN87~y4+%q^xU$5ftnrsz z$;IAICSgVtytm^g3;p8C@b(o)gQ|#L*TqIj-6?vuzC?rQn_qTGUAr4X83Am^RR=kNd$>lKlzOOB7C&HdogT^ zGKeRBlfz#yKgb6OIC02I^|O2vgvv*h4m$siPLU$l3p-%APNO2GBh^j1Cvey#Q7ze7 zWpP4IM+SABqkPbAx5VbUbUXaTm1d3)2=!hZG@)};Hz+ioFEEbQL7Tv4Uz~^f}jk)l$Ad z+Wm%l<(o16PCf(VYSl%wgHQSgWBTQfQ#T7@?YU;0es9kdoxPst0=XKolD`1j?nZO$SJ|OYs5rem-mU`AJLzB4!yHjO< z?FCm1tH-l-Jl&@R#;!ukW{pj8aL75QCp&N9K|Lr%*Xgs?FZETV6UrNr&uve*Ei#=8 zjw6|0u{<*{ILK(-r7Ts*ybB{{i$r9tEQ=0TP;yBe9{h*ECj{Rd zS@-{5c;DZxc$KVf`4(=&7iu(@@cxn)H?IF?8R$mMg7b&>1Rue! zDZnt*bR%=-yooj5;OcLcRIhM-dd12<#2U-OYepxV8y-#PT1OTmu)a|;Y&zs0ft)_&0;_6!Lnqxfh_ z5YV6vK_nkz|7iYpgD4mE_x_n8vyL;&Z(c10qHEI)di~qqOK!6OlgaNMLPN3u%*QxC zGj=8*m-KVt1pW8Jw@~rlq5iLN{W>!9-{tgQ0QoOu!XW(DN&HVf`Tr{?0pb1LP6%B4 zUrBA(28dM@-rju>iN|jHX#=pNUql&&k4Oy;)H~6FB5B8IkLg z#xTr0IgVJfU-8iG7>G)OJ_d{zPhtM)+wC)6?%*5T8|eR%5xE_Km$F`hW_O`$@cxY8 z&+mIbf~$b+ci|X_*x~&BGBQ7?fZ;D|BHJ+k`b4t__%y*$#J}*b+cA7j#Ye*Y)=M+= zPxF5cJzp@)ZCDpln!)~iZ~yiIG=Vg`)jr$PUA@uX;78B+ZC85M^K<_Cp7%>kHixbE zVDbf}Q@GYof#cA?*svYp3$7dZU^?jsEe>Rfx0FrNVNNHD&e9Jumcp-IL(tDncV$*@D}L6d-~Ft z{QtZ>yaWb}Kn$q08M61Z-x!VnH6wLD?fmnvd6~(G{&g_&cEX50z=>v3ygsqD`)$-v zFg6(PS*=neQ@F-Q^gpUsW(oyi{U=L7G_(D2^k0^XrmTB?dG_3NkZkm7+U=lsu*^_G zg&Pxmhwqcu?eUCO*XZZ}Vj~s6MtrulEs_6plPDA>)onjOGKuZmh&~vVETsbF|BsCU z+rnk0Llm9S)KND&D?Ra7oRu8xMab1 z{|wRXzYvM}K+Iw}{C8sIb}IPp!g1JdklkD!gcZcdWe9gZHcp!V7fbxrzd_2M7IJvR zsq*()^^ymsD9#yL^7mAM`5HzH7d^`D{+F$^+eH!n0zB{-cFFedRSj9%`WOR{LkecW z_`RloH$VIl3_hnrG^t4FCrcwxi53Z#!q3V1V@y9^%mZG$ZYOa>^P?nhhKTZS_-i&r zC5muSA3Kx%=c~RjARiOUdwnVZRv0yd}Rnr{i(camN6HCU>woZ0p9UDGO+%$*ib~g4ZCAIX^ZO@%u2;$Xa+xOu>Oy> zS-d5Wz{v^CdJI0qR|>4CEm>p@^740}@hJBl{+eFV7rJeMn9pB+MHzjpqG{vHwcC8> zkD*WQyU*`sDH{oynepEcP)6X8V&p*V1rdS(-kfn70#M$WXlnx>-r{k)4BPh`vn$$p|qHOb+Rw&d_iexfzUvN%OV!A@9~t8%Mw{p`XPj&@nRvyJi%JJ2q%}&-iU1 z-XOdg%nbS`yBcPY(53;FB{niW^?UaR49i{E_2Va?b2vA#UP5VKtvX z@E4gxU{2uhkpEuKkOxsjpusenHZUt^wo`$lEW%XM5d23|1%?IV_@vqo3Py|^JepUV znD{%1@oQbNg+My}x%xpY*fYbIf*cg|A_1cp$Ppw9ObAScXlAxoEAc{@ z11J*@9DdXW;2EI}f&qLm5SB65xBMMge;#<0Ldnq%^3L1tLBTvmVRJhzF`uqg`g;l6 zE`(`1W zOnTWF+4j>V{{BWMO^IPQmh<^~j#jk}s4!ihuB-mN9-FJ&be!DsV$>oM5(b(&#QyVk zk5ySD9&U_uk9>y1)+7QY^nd8qPapdKX5Lj+vrj<9|LbHMP;cA~3Qbr4-^|+^l%k){ z{=o1U;+kO9^!dlr@s-`%8Y>k@0VUkn0-*FhOQ*@>&r<)%H6bI*ER8S02$^bq$W(KN z4M3(E1^gS7zWh2G4Vkg;D)j z1SK*<33;`j8~5SaQq=upR|sU)&7}q%FF{H6wN2S$T4S)fn^HkZEkr)ScG3C-tdS@$ zi?hb}Z;;soFhMt|e?*E(iTN8qOv)A@kW{G0$`g=a--B=#4?;Wtzg`Y8UkC6vN4bRN z0ChLyM1~;1k|ydUn4jK9q6kcs2oCg`dd?c*9~8U&mp2>IEf{B{WCD+P%GZMlxLv+h&w6ZvWWsQsElAtn z9%dK~!)1NqBnCEiUIN&*1;zSsn`39G%`*+I5Q_666wtx0T)B))Pzc(>q2UypsqB}}4-gnIY>WKu6aeaN>9ua_+U_JyEbTbKZjdHGpd`u# z2A|+4?-y)_C4f0XH4pBch!oW-)%ShxnC5F)ZrmSx(8tM8(N!t~Ik`bB39#`If)E5= z@&LSK;fsv@Gvzs&b7Q3j`YU77No@0;+0)}ND9Jd1V@(Yp` z24nXcFp`mRE5VRo*oZ)6y#a?zz(obh&+Xli>F)f$U%ak?3B(aDfoU0TJwT}? z3zYGPX5=h>Yg`x0e4_bj;OLVP!H>fI~}2y6V#cMzjDiqI#a?(0`1m z9u?vbUT;s0k9>-!#bvk6{EgInt|Uh{P*6}eH=B-r#lIE5x02!Bql@5n%wo|?+c;KZ z1q#^ENAueR*41(P<7-a^VuxbIEm*==V9;(CEe8@zSk0c*c5fR+ZB3IqCNA;zx0aLc z^z>a)U`Jk_xUWWeW(i^eIaF3vFNKPmh-vn+{Pqz%Y@O#tjIO{HQar|TGu-<8p z#2w&tLTxw(g-nX1fIsrc>v-l@SR;jM3JU&j>=D2)zROq4OOGGeTkN1#%2!O_z~F19 z^De3Pf8PMV#Rmloex_v7#G)u(9x|H%LqF`KO#S2u^XU$P7t}vLP6*zk#X1T=Kfv4w zmtLJIf!no8ikk-VJfBRnnMeRufHzU-g0 z*9nLowQIE{fJfXSyv`+(T&|RzycDUI9wI-Yi8Cjl4ZfMt-pd0`%ju3|7y@1(AazYVfr%O%KuQ9% zlJA*8dj*x;qG^NMS>zL~T0y|8GF?x$(-C6t#QU$n?=7z2iwzi7^K4Y#go`d zPdkzpI>M~D9>1MWYVfA&7`Zmd0c{C<_p+bqUj=L{QO}5gH+H{ytvy z9cUCNXjW8N&j5wl&*)79d8=Vu{|p`&$WIB)lhsyvAgE+%6%Ii21!D=vtsCk-%nN zo&*3x6herCzUYAiMB@VAGDK81f}hv>IUlUZ@$o@{NUZ0p;4r!5ds!cW`G;wsm6fmj z@4iCw3ODO@Zz+Tr1W_jJu+GA;JNMqbWS4882=LdSdT}D`S}k7I;ZgD+30!wHl@oh| z|Lw*2kCBw)Ul-%^=Znc=Ju8n4?@HHCWU(*-Dl^$@f`J$TcaiZ#K^rPw@wyzE;BYz= z4)(z;167ZrXnaG}Ovz}fi1sgQg`@MW{zKqIEN0!^2RH~Vd&7AOiUrE_xZaTu42fg7 z6`M{M?qx9;vzU+Z`QF3+K5XO7E&FP;NJ=CWuhi5|F@oLBXyTJiMtkc)81FTTc4_GO z`sDsWO1cnIl6pN4l*0VApA>Y zQB;kC@=@H}eqeMLvKEMm)2`8SwpRmm)`ky62!1c0KkEnzwosVw3V#Z{$x%R*`7H2F zxEWfsP){LAW*J|{8xs?4UZ6h##C0ehqlHirSAseDoT&7}H$O#J)OUEbKlL7iW;sd8 zxpq>2A}hm3tvY3&n`K);ggg-3rh(G+ch+CH{KKHMTV@pw2!mPER6U{vX7Rf;6>_LY*y z7MT>XCBJ|hw|;<1=!G7|4C^X9+iJiSHnvF!QTb_HLsEF$CoE0$Qy%$O(yJ6DvCR$# ziC^rY04XXGX(<&yh+Lrk2l? zRT&y7P{{+~8^4JHYYCL&<*5xZRsJR^zo^ozPG9+>Bu+=mg{O6w#i9`;3-{&AKe$6& zc&USPJwQh^e+xJ)R2rQ=xXh?5jx@;V zgA{{KVQzZl#aFOPX{Ja!S+z* z`QDHjvC)@Q#J<*Jxm1IAn3>dqFYEZFRwn?#_I@R(kfT%ZB;t@6;3@=d4&RC zC&ay(?iO|mZ_U@h`9C#ZqRa$XLsUjm;t_lWMl|_xJD~J(`9+0&xiiWi1VqApT*#oB zpO$~IOq4PQ_>gzyydyB?f=GEFv5ijb#%R<-CG%~?j4X1)_cPE;lD#@VVs;;=bsQ_v zOW<(#Zg3orQc7+QBb1TKv=cw-Oy;NQPaaK^7A}UjuS?PqoJa_zz4T`3>%u2qxAUO55Eg3K*E?$H@Zj)8|)`1)$Uo8iG(t%Rui?i@5oDoNGw z8nb=b4M3-)vR(R`;Nt2F(q}+vFfMu}T_|Y62T>U(q7(>><;9L8Ny0)94+G}Kix-DL z2_egzqE!U(Q5N)_yN{G6KT0IAJ!=cW9kt}dfpq83Pctq;t`N$R3zr;!a(OYu!y??h5W6xj}h{|PviF72cz$ZxO+$1?QYFAqi>3;M%mjutEzA-plUE|7S22^mOXBrRIj#N-Mh)H5PZ$Oo zWf!d36BNcBTxC*N;*q6LK2c`GE)pYQ{ta-s0W}CBKBUZ7m=3*b9mp8w! z_Y0Q2Jww1oNJKh(^E!lqBW!j>$ek5PYOqqZ837Zzjr0eFwCkNBK9jtG2B7i#cu}0O-)Dg zmAddUgo1cjwVM*oH?M!r3Q2n_>+EOZV58Cls8)%n;5AH-8X!-x7PuXFA%-!A-= zR2~BR?E7V(U?%p@U@l+G^(L}DA#c%s2q>unZ0kD|`7;4b|M{m;{3MxOi&_mXO7EgH zsx7lX))R$P;v+B75Z;jQIu%!Rd+j?!LG$ zYyN7Z@Kd{gXvwZ2jJLNp6WF{Ifb~VfOb5V?Q)QE-4l9StjwX1j?Ix4)7Our>aP|K*T);S4Wj0r(`O0N|bjbBdz_WnZ2qjR4s?TJBbo%tp5} zc|5Y>`xJ)3{1||0BvD8w<-P|P7wU0y<(Rcag^mL*wVBBf347O%xBO58>?>pg3doM3 zJXv8XfM=(c${gf}7kCR}Vs7{By&y3ygRdx;}p; z(UGu|e%_mbTKi3(RccR^AzZ5jAVvys5lBslwXATX5&LZb~D+qiHLEee|r=SHk( z3*TqHTgwkrMohqdpE!MLxeHL0TzD8Q`kfpoKGwRvjfVi5mt=D?1OZ~G7m@yAOLKX! zFfcGCL)lV(L&X}^Pe5SanJq>CCKopZ7L_1zM=#pRyb&G^Ja2_6+vNWUfLZcgfMc8h z!+&|=TC))vv+=SlKiJV2Dmi|I)m+(1GbIk7U!wmxeHy@7%s!Z&wkR!IA+V3I1_DIe zZ}XL4t+2qi;0Ab|4>T4Xi-Ni2N#?K@kADTWJWU4DUWkv&8G-%W;dG&UA!2|J<^hkD zz~P9PhgTafz*e9D?Dt*2BXeTo<-P0*z}hS;mp3dJfx3T2po@2Axi>LRyTK)E;A<4+!%|A})U0*7Qm?KV+J=G0<Q#=VSL|Mntb_0$=&j<|WA0;-fiZZK_=ft{{-L2&T(dsojzB%n|C_&XB{Tzk zT$`$~^LwRh>BJa%47dEqI!JwiJzr#(i{p>- zy}Y|2_?)q)Qer!2T?9#V721t%YNaa1B>bX48u`g`PrL&610aC7(`|RPRo>5JVe9SN z1-2T@xIAh?+Ki9jxrz*+D?lukq$Cf{EC{K-bX`oulvg`k$yqxjjzK3E7X)sU6nVNp z25e4X&ifx3uYi1aq^hcL84vKK-)BjCP#~1_B}~yF1mYwyRsO+fC}=BU>x*LxoTs`_ z?x(vgMq(gDBmy|sMl1->A;j>O`7E5U5GbUcvPw8`%s;y|6o?ZQ^qAcxLxT)ZtTqbc z_yZ$UK_U=yYO@5T0=865jKXgFvMZAO9*`6b0^1alAVx-iLzr5e_pqj0I~?x`NcC5+ zES9|g1o&9uIk<<7KWVn}gDnt6ChJqb0*a#M?aGe92*fcv{r=sXz6M3Bj5=q#&c|Dl z;Y8kSD&BD2AU*sI1heCUQD9?Oh*r69VAy6JWQn3V?9CF-!I%ZO3I?DFA*4Vvn=4#K7pM(a1YE!RH{)Ro_Vzmv;rU? zDh#W+9D`(~NLrhFOS68B-|k(Q%fvCD+<89^XkByXtCpslfMsQ7poTJD?_6$8CtKa= zd>swZ8jYXaKOST)v4q%@+Ca9(gfu0-rWE)n+d} z7uz3n0cleK@8MiR*S4}|ClIS=v8VeericLA6u@mHka)_ajwQF)9`cF}*7dGVokMR) z2yHyE=U9}2DCEUw0kvQ^1(&CRGR$C{%{BqWh@pAtOKqa;?^;m3jgAmavWP$P~zJpr1(E}iT9CA7(#Tej; zutbOf_Bv!dFZcWh$ss+2vdfdpxSP!FsvI9Sem^LUPOu#urnkh#5<4?jb)}}`&*!4> z$Fu~sJb+V56xq5MT4`YEEmK}3Ie2-KBY^zL7ZzPKNWMTfflswTem=wuhxz{Bsb8*W zK3!cR5dF7WAG;MQfekZ!q`axPj;G?_KYWlGvw%?^y5O(H-4$gPyZWlP3y zc~Hp*Uw3mk@B5S)c4xD<+5`D|#ZT6H4S7`M#+3cWeM#&K`42q{w!di7DCQDcGI0MfvHSab{`8G@tD&bIuk`k4 z#3IQS2vccsL52_^J~2MA(ep;zM{1>9laSYguCLk9n+t@;(a#KobKVrjyEaoS0HX+D zJO0E7A$UHqd?pa1z)@dVga4Z-nZl0A88mxA`-!O7EOA+~iXTs$mNRjhT5|$+1cjg~ zwR3Mh6firoXAtk$0>Wn~*!uSXEf{7r=^lV+umI7gofdN@SNkfmdExKTdi|S%)lLjx zf|DVF!2BO*86QF{jROWne!3z{`OZD!5h>gdz1y7TThQR6wBFB(kl*-(h%zC|RH;P( z>+cK$B?2rwyaHfOo!?4d8u5Jz!VbHq8QV`Z&uyh1=ioq01pn9`P1H{kzFI2*EhafxydS=QA4v zYdJsuft%mB22w|J4Y!SrX8ON0*6oegV@ObV{zU>h2==cmZ;8Mh*a2kHZH7AEoqvme zZ_&(y$Oh~iMw1938Qd8LSa(Wy3sEk=Aga9t0uOU9fStMw80qETf(2i5JcLF*Nc({F zcbiv%qyg*zz_zf*8hu57$dOV27vwb_e*5|_P+2C zkj-=4nZdxoSZdz5-O>0uJDUoa>UAj^JLFGK!+>$IHeeLq#R7p63r_1TXv3od2!E02 zn|J#gKFl#>{>Z272X@%tWLL@reoH0M{Xd+&byStz);_F=ilBg`Agv&c(w&kL(%sTXcPfoE(kWfi z-HL!TQW7c%2uMkHeRHeNInVohzwh_QH-=+8hq2Gt`@YwjYsPh5bFLqVsF&_`L$4gM zHvjyi`e*-^J~2bOJ6db48^~@7TLBIeVa6B#7c+i$`EoO2eTm-*WB4SARbg;a3LJ{PvEajE+q5cmhpAp z@cnYMErag?0wIDEsttKHIxFzX}z1jeF2FC)TGy~2y<^D zUfKzzOQ+!L+Qx<)AcZxC!RCm^`OiNH>+j#cAB7>?sHmvJf}s1<3;fceKEG%64{@ILQ+5#jHqEzjS+A2y#sMr zwiX-o&El>296#jW0ax%F?(}#q2v?AK*w3o5p&VX%i7_a9_>Q*x@!y3)@kUDT9Kb&3 zV5__5r*wt7B)M#(A&z0m*%0C}GMgf0N z@*aUUi~siK(wIL-@-T=6ym&`hfs>U(pye0CxiU{YehDJPM0{y0*&U`rghG;NWhtYh1@f(0;6ae^T(*|-_62!El zqZyZwrV(b!@vHFUGt{V_mXDZ!Tg{6Ngllkj563-0hu3SV99N_H{ed7NsIL%C!!O$T z{SE}kmsQaUm{s-_QaP+i76wu^OHdE!{6=lW!|!SUJfej5Jm9+YRX7TkSh*;F*8LG; z|NB(*Gs!R!zCtAQChD(8aAkUPb5~6toxv1}i{#!*xAsw}tNt zoBQc~lwlBthZkjU6Un_yJ|@DBI`YSrZ=%4r@);yQl*9z3Axc3;TYxOW@DR2a1CNv! zBuxP6vwtuOA|HE*jSROy04N$saC;FFe}}yl8Aa3@G8mULsePLpJ(rx;D2EMly@? zFC%$~rJ#Q2Rso9rT>FicPZ?KIx$HXw*3QpPXkhR+``)h-SZAxYJ2O4V!p9tz3)Om7 zzu-H^&O&Xk|M}7xa`@AEpO9{k4`CwKgXIRlKLC0FK442B&rbS*_(AG~8%QO9@s%MY z1OWRWN@~J}wWyx7g4tBE>;MkiuY*#^ZJH9m(t9sM-h@Lbh0~M7RuubGkC6(}2e!*M zsQxaWUNkJuc@|NT)M1=n{#ppQVA&E^0lk{slmBcuM}m*IFm z?c{qaMb_k}gud16yrDByZ~uVLW%C8Iai7GM>*!UqczwMH%X517hVZX*5Eg>Prhb!w z2Df;?CnOc}2~>ZK^&`SqlNcZ|i3Z02k4J@4ry=nTYO<|_mEHB)nPET{#*}BHo&6&T ziHM{j6~vjYL8R9R{vQ-K)F5AldT-@v={)3>h?`arFi~9v2M8X6sUb&<;j}fdjd+R? z9!^Gf+om?MXX)O~WETLhyZh^!x9kN3S|6TynqCtj*MjxBh2K|q(~E!WkF8F2ZUY!$1#d$)XoI@$o%K$Lgf(Sfs}#*P2eTM2w21lLa-l%Z<;I8 zrabdHoV|WqOo@FJyv$g!rYhqr3pGDD?1Qh4p3k&=qf#pOcnuJRB1{d>2Z#iR&0MK| zYBqK-{S2=0pe7@G0eUp!adyyHfZ*cZBd62Dt@Wu45TD()35@LVp+U=A^K@J2!F8|A z1#(OQ;J)>mtoLrPzZ%wZ4-tVM{tY7VLi+=Bmg|>I;1lk9eolWrz(gU2H_e#O-M9(| zpk0YOu9QkLft-{Z>*5U>uL&8#~4~=+Q3EM>lnEOih0~sLmXZR zsYx$W>r=P`V^@aS_k{>j3628SGp3L{qPBREBkm`sdhdae57up!NCRUkG+DzozVXK` z5Fk!&C%=9i873jZC5hbf{KqA6!TSslB$fka9uWQP%0DLx4=`Z~qxV&CstD&)a1?xf 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%0AH^G^!WX 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 V-l3DY;05p_Fa1cW5N_!Ge*hLur_lfa diff --git a/launchers/chat/src/main/resources/META-INF/spring.factories b/launchers/chat/src/main/resources/META-INF/spring.factories index b61047efd..2821fcf14 100644 --- a/launchers/chat/src/main/resources/META-INF/spring.factories +++ b/launchers/chat/src/main/resources/META-INF/spring.factories @@ -1,7 +1,8 @@ com.tencent.supersonic.chat.api.component.SchemaMapper=\ com.tencent.supersonic.chat.mapper.HanlpDictMapper, \ com.tencent.supersonic.chat.mapper.FuzzyNameMapper, \ - com.tencent.supersonic.chat.mapper.QueryFilterMapper + com.tencent.supersonic.chat.mapper.QueryFilterMapper, \ + com.tencent.supersonic.chat.mapper.EntityMapper com.tencent.supersonic.chat.api.component.SemanticParser=\ com.tencent.supersonic.chat.parser.rule.QueryModeParser, \ @@ -20,5 +21,8 @@ com.tencent.supersonic.chat.query.QuerySelector=\ com.tencent.supersonic.chat.parser.function.DomainResolver=\ com.tencent.supersonic.chat.parser.function.HeuristicDomainResolver -com.tencent.supersonic.auth.authentication.domain.interceptor.AuthenticationInterceptor=\ - com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor +com.tencent.supersonic.auth.authentication.interceptor.AuthenticationInterceptor=\ + com.tencent.supersonic.auth.authentication.interceptor.DefaultAuthenticationInterceptor + +com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor=\ + com.tencent.supersonic.auth.authentication.adaptor.DefaultUserAdaptor \ No newline at end of file diff --git a/launchers/chat/src/main/resources/db/chat-data-h2.sql b/launchers/chat/src/main/resources/db/chat-data-h2.sql index 65155e3e4..8c65577f1 100644 --- a/launchers/chat/src/main/resources/db/chat-data-h2.sql +++ b/launchers/chat/src/main/resources/db/chat-data-h2.sql @@ -27,6 +27,3 @@ insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`, insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(4, '2023-06-10 10:41:18.589','alice 访问次数','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `pv` FROM ( SELECT `sys_imp_date` , `s2_pv_uv_statis_pv` AS `pv` FROM ( SELECT SUM ( `s2_pv_uv_statis_pv` ) AS `s2_pv_uv_statis_pv` , `sys_imp_date` FROM ( SELECT `user_name` , `pv` AS `s2_pv_uv_statis_pv` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `user_name` , `page` , 1 AS `pv` , `user_name` AS `uv` FROM `s2_pv_uv_statis` ) AS `s2_pv_uv_statis` ) AS `src00_s2_pv_uv_statis_b825` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_pv_uv_statis_0` ) AS `s2_pv_uv_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"访问次数","type":"BIGINT","nameEn":"pv","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":2,"name":"访问次数","bizName":"pv","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","pv":2},{"sys_imp_date":"2023-06-04","pv":2},{"sys_imp_date":"2023-06-06","pv":2},{"sys_imp_date":"2023-06-07","pv":2},{"sys_imp_date":"2023-06-08","pv":5},{"sys_imp_date":"2023-06-09","pv":2}]}',0,''); insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(5, '2023-06-10 10:41:48.211','停留时长','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(6, '2023-06-10 10:42:02.184','访问','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); - -insert into s2_plugin (id, type, domain, pattern, parse_mode, name, created_at, created_by, updated_at, updated_by, config) VALUES (1, 'WEB_PAGE', 1, '访问情况', 'EMBEDDING_RECALL', '访问情况', '2023-06-11 19:36:47', 'admin', '2023-06-21 15:26:46', 'admin', '{"params":{"487C128A":"2"}, "url":"www.test.com"}'); -insert into s2_plugin (id, type, domain, pattern, parse_mode, name, created_at, created_by, updated_at, updated_by, config) VALUES (2, 'DSL', null, '', 'FUNCTION_CALL', '访问情况', '2023-06-11 19:36:47', 'admin', '2023-06-21 15:26:46', 'admin', ''); diff --git a/launchers/chat/src/main/resources/db/chat-schema-h2.sql b/launchers/chat/src/main/resources/db/chat-schema-h2.sql index 2842efa02..b0bb7d909 100644 --- a/launchers/chat/src/main/resources/db/chat-schema-h2.sql +++ b/launchers/chat/src/main/resources/db/chat-schema-h2.sql @@ -121,12 +121,14 @@ CREATE TABLE IF NOT EXISTS `s2_plugin` `domain` varchar(100) NULL, `pattern` varchar(500) NULL, `parse_mode` varchar(100) NULL, + `parse_mode_config` LONGVARCHAR NULL, `name` varchar(100) NULL, `created_at` TIMESTAMP NULL, `created_by` varchar(100) null, `updated_at` TIMESTAMP NULL, `updated_by` varchar(100) NULL, `config` LONGVARCHAR NULL, + `comment` LONGVARCHAR NULL, PRIMARY KEY (`id`) ); COMMENT ON TABLE s2_plugin IS 'plugin information table'; diff --git a/launchers/semantic/src/main/resources/META-INF/spring.factories b/launchers/semantic/src/main/resources/META-INF/spring.factories index 91b5a4e62..1d40fdda2 100644 --- a/launchers/semantic/src/main/resources/META-INF/spring.factories +++ b/launchers/semantic/src/main/resources/META-INF/spring.factories @@ -1,2 +1,6 @@ -com.tencent.supersonic.auth.authentication.domain.interceptor.AuthenticationInterceptor=\ - com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor \ No newline at end of file +com.tencent.supersonic.auth.authentication.interceptor.AuthenticationInterceptor=\ + com.tencent.supersonic.auth.authentication.interceptor.DefaultAuthenticationInterceptor + +com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor=\ + com.tencent.supersonic.auth.authentication.adaptor.DefaultUserAdaptor + diff --git a/launchers/standalone/src/main/java/com/tencent/supersonic/ConfigureDemo.java b/launchers/standalone/src/main/java/com/tencent/supersonic/ConfigureDemo.java index 43ad1e99a..e93264adc 100644 --- a/launchers/standalone/src/main/java/com/tencent/supersonic/ConfigureDemo.java +++ b/launchers/standalone/src/main/java/com/tencent/supersonic/ConfigureDemo.java @@ -1,63 +1,198 @@ package com.tencent.supersonic; import com.tencent.supersonic.auth.api.authentication.pojo.User; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.*; +import com.tencent.supersonic.chat.api.pojo.response.ParseResp; +import com.tencent.supersonic.chat.parser.ParseMode; +import com.tencent.supersonic.chat.plugin.Plugin; +import com.tencent.supersonic.chat.query.plugin.ParamOption; +import com.tencent.supersonic.chat.query.plugin.WebBase; import com.tencent.supersonic.chat.service.ChatService; +import com.tencent.supersonic.chat.service.ConfigService; +import com.tencent.supersonic.chat.service.PluginService; import com.tencent.supersonic.chat.service.QueryService; +import com.tencent.supersonic.common.util.JsonUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; +import java.util.*; + @Component @Slf4j -public class ConfigureDemo implements ApplicationListener { +public class ConfigureDemo implements ApplicationListener { @Autowired private QueryService queryService; @Autowired private ChatService chatService; + @Autowired + protected ConfigService configService; + @Autowired + private PluginService pluginService; + private User user = User.getFakeUser(); - public void addSampleChats()throws Exception { + private void parseAndExecute(int chatId, String queryText) throws Exception { + QueryReq queryRequest = new QueryReq(); + queryRequest.setQueryText(queryText); + queryRequest.setChatId(chatId); + queryRequest.setUser(User.getFakeUser()); + ParseResp parseResp = queryService.performParsing(queryRequest); + + ExecuteQueryReq executeReq = new ExecuteQueryReq(); + executeReq.setQueryText(queryRequest.getQueryText()); + executeReq.setParseInfo(parseResp.getSelectedParses().get(0)); + executeReq.setChatId(parseResp.getChatId()); + executeReq.setUser(queryRequest.getUser()); + queryService.performExecution(executeReq); + } + + public void addSampleChats() throws Exception { chatService.addChat(user, "样例对话1"); - QueryRequest queryRequest = new QueryRequest(); - queryRequest.setQueryText("超音数 访问次数"); - queryRequest.setChatId(1); - queryRequest.setUser(User.getFakeUser()); - queryService.executeQuery(queryRequest); - - queryRequest.setQueryText("按部门统计"); - queryService.executeQuery(queryRequest); - - queryRequest.setQueryText("查询近30天"); - queryService.executeQuery(queryRequest); + parseAndExecute(1, "超音数 访问次数"); + parseAndExecute(1, "按部门统计"); + parseAndExecute(1, "查询近30天"); } public void addSampleChats2() throws Exception { chatService.addChat(user, "样例对话2"); - QueryRequest queryRequest = new QueryRequest(); - queryRequest.setChatId(2); - queryRequest.setUser(User.getFakeUser()); - queryRequest.setQueryText("alice 停留时长"); - queryService.executeQuery(queryRequest); + parseAndExecute(2, "alice 停留时长"); + parseAndExecute(2, "对比alice和lucy的访问次数"); + parseAndExecute(2, "访问次数最高的部门"); + } - queryRequest.setQueryText("对比alice和lucy的访问次数"); - queryService.executeQuery(queryRequest); + public void addDemoChatConfig_1() { + ChatConfigBaseReq chatConfigBaseReq = new ChatConfigBaseReq(); + chatConfigBaseReq.setDomainId(1L); - queryRequest.setQueryText("访问次数最高的部门"); - queryService.executeQuery(queryRequest); + ChatDetailConfigReq chatDetailConfig = new ChatDetailConfigReq(); + ChatDefaultConfigReq chatDefaultConfigDetail = new ChatDefaultConfigReq(); + List dimensionIds_0 = Arrays.asList(1L, 2L); + List metricIds_0 = Arrays.asList(1L); + chatDefaultConfigDetail.setDimensionIds(dimensionIds_0); + chatDefaultConfigDetail.setMetricIds(metricIds_0); + chatDefaultConfigDetail.setUnit(7); + chatDefaultConfigDetail.setPeriod("DAY"); + chatDetailConfig.setChatDefaultConfig(chatDefaultConfigDetail); + ItemVisibility visibility_0 = new ItemVisibility(); + chatDetailConfig.setVisibility(visibility_0); + chatConfigBaseReq.setChatDetailConfig(chatDetailConfig); + + + ChatAggConfigReq chatAggConfig = new ChatAggConfigReq(); + ChatDefaultConfigReq chatDefaultConfigAgg = new ChatDefaultConfigReq(); + List dimensionIds_1 = Arrays.asList(1L, 2L); + List metricIds_1 = Arrays.asList(1L); + chatDefaultConfigAgg.setDimensionIds(dimensionIds_1); + chatDefaultConfigAgg.setMetricIds(metricIds_1); + chatDefaultConfigAgg.setUnit(7); + chatDefaultConfigAgg.setPeriod("DAY"); + chatDefaultConfigAgg.setTimeMode(ChatDefaultConfigReq.TimeMode.RECENT); + chatAggConfig.setChatDefaultConfig(chatDefaultConfigAgg); + ItemVisibility visibility_1 = new ItemVisibility(); + chatAggConfig.setVisibility(visibility_1); + chatConfigBaseReq.setChatAggConfig(chatAggConfig); + + List recommendedQuestions = new ArrayList<>(); + RecommendedQuestionReq recommendedQuestionReq_0 = new RecommendedQuestionReq("超音数访问次数"); + RecommendedQuestionReq recommendedQuestionReq_1 = new RecommendedQuestionReq("超音数访问人数"); + RecommendedQuestionReq recommendedQuestionReq_2 = new RecommendedQuestionReq("超音数按部门访问次数"); + recommendedQuestions.add(recommendedQuestionReq_0); + recommendedQuestions.add(recommendedQuestionReq_1); + recommendedQuestions.add(recommendedQuestionReq_2); + chatConfigBaseReq.setRecommendedQuestions(recommendedQuestions); + + configService.addConfig(chatConfigBaseReq, user); + } + + public void addDemoChatConfig_2() { + ChatConfigBaseReq chatConfigBaseReq = new ChatConfigBaseReq(); + chatConfigBaseReq.setDomainId(2L); + + ChatDetailConfigReq chatDetailConfig = new ChatDetailConfigReq(); + ChatDefaultConfigReq chatDefaultConfigDetail = new ChatDefaultConfigReq(); + List dimensionIds_0 = Arrays.asList(4L, 5L, 6L, 7L); + List metricIds_0 = Arrays.asList(4L); + chatDefaultConfigDetail.setDimensionIds(dimensionIds_0); + chatDefaultConfigDetail.setMetricIds(metricIds_0); + chatDefaultConfigDetail.setUnit(7); + chatDefaultConfigDetail.setPeriod("DAY"); + chatDetailConfig.setChatDefaultConfig(chatDefaultConfigDetail); + ItemVisibility visibility_0 = new ItemVisibility(); + chatDetailConfig.setVisibility(visibility_0); + chatConfigBaseReq.setChatDetailConfig(chatDetailConfig); + + + ChatAggConfigReq chatAggConfig = new ChatAggConfigReq(); + ChatDefaultConfigReq chatDefaultConfigAgg = new ChatDefaultConfigReq(); + List dimensionIds_1 = Arrays.asList(4L, 5L, 6L, 7L); + List metricIds_1 = Arrays.asList(4L); + chatDefaultConfigAgg.setDimensionIds(dimensionIds_1); + chatDefaultConfigAgg.setMetricIds(metricIds_1); + chatDefaultConfigAgg.setUnit(7); + chatDefaultConfigAgg.setPeriod("DAY"); + chatDefaultConfigAgg.setTimeMode(ChatDefaultConfigReq.TimeMode.RECENT); + chatAggConfig.setChatDefaultConfig(chatDefaultConfigAgg); + ItemVisibility visibility_1 = new ItemVisibility(); + chatAggConfig.setVisibility(visibility_1); + chatConfigBaseReq.setChatAggConfig(chatAggConfig); + + List recommendedQuestions = new ArrayList<>(); + chatConfigBaseReq.setRecommendedQuestions(recommendedQuestions); + + configService.addConfig(chatConfigBaseReq, user); + } + + private void addPlugin_1() { + Plugin plugin_1 = new Plugin(); + plugin_1.setType("WEB_PAGE"); + plugin_1.setDomainList(Arrays.asList(1L)); + plugin_1.setPattern("访问情况"); + plugin_1.setParseModeConfig(null); + plugin_1.setName("访问情况"); + WebBase webBase = new WebBase(); + webBase.setUrl("www.test.com"); + ParamOption paramOption = new ParamOption(); + paramOption.setKey("name"); + paramOption.setParamType(ParamOption.ParamType.SEMANTIC); + paramOption.setElementId(2L); + paramOption.setDomainId(1L); + List paramOptions = Arrays.asList(paramOption); + webBase.setParamOptions(paramOptions); + plugin_1.setConfig(JsonUtil.toString(webBase)); + + pluginService.createPlugin(plugin_1, user); + } + + private void addPlugin_2() { + Plugin plugin_1 = new Plugin(); + plugin_1.setType("DSL"); + plugin_1.setDomainList(new ArrayList<>()); + plugin_1.setPattern(""); + plugin_1.setParseMode(ParseMode.FUNCTION_CALL); + plugin_1.setParseModeConfig(null); + plugin_1.setName("访问情况"); + plugin_1.setConfig(""); + pluginService.createPlugin(plugin_1, user); } @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { try { + addDemoChatConfig_1(); + addDemoChatConfig_2(); + addPlugin_1(); + addPlugin_2(); addSampleChats(); addSampleChats2(); } catch (Exception e) { - log.error("Failed to add sample chats"); + log.error("Failed to add sample chats", e); } } + + } diff --git a/launchers/standalone/src/main/resources/META-INF/spring.factories b/launchers/standalone/src/main/resources/META-INF/spring.factories index 4130a3f2c..c433857e6 100644 --- a/launchers/standalone/src/main/resources/META-INF/spring.factories +++ b/launchers/standalone/src/main/resources/META-INF/spring.factories @@ -1,7 +1,8 @@ com.tencent.supersonic.chat.api.component.SchemaMapper=\ com.tencent.supersonic.chat.mapper.HanlpDictMapper, \ com.tencent.supersonic.chat.mapper.FuzzyNameMapper, \ - com.tencent.supersonic.chat.mapper.QueryFilterMapper + com.tencent.supersonic.chat.mapper.QueryFilterMapper, \ + com.tencent.supersonic.chat.mapper.EntityMapper com.tencent.supersonic.chat.api.component.SemanticParser=\ com.tencent.supersonic.chat.parser.rule.QueryModeParser, \ @@ -21,5 +22,8 @@ com.tencent.supersonic.chat.query.QuerySelector=\ com.tencent.supersonic.chat.parser.function.DomainResolver=\ com.tencent.supersonic.chat.parser.function.HeuristicDomainResolver -com.tencent.supersonic.auth.authentication.domain.interceptor.AuthenticationInterceptor=\ - com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor +com.tencent.supersonic.auth.authentication.interceptor.AuthenticationInterceptor=\ + com.tencent.supersonic.auth.authentication.interceptor.DefaultAuthenticationInterceptor + +com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor=\ + com.tencent.supersonic.auth.authentication.adaptor.DefaultUserAdaptor diff --git a/launchers/standalone/src/main/resources/db/data-h2.sql b/launchers/standalone/src/main/resources/db/data-h2.sql index b09845661..27b296b13 100644 --- a/launchers/standalone/src/main/resources/db/data-h2.sql +++ b/launchers/standalone/src/main/resources/db/data-h2.sql @@ -4,21 +4,6 @@ insert into s2_user (id, `name`, password, display_name, email) values (2, 'jack insert into s2_user (id, `name`, password, display_name, email) values (3, 'tom','123456','tom','tom@xx.com'); insert into s2_user (id, `name`, password, display_name, email) values (4, 'lucy','123456','lucy','lucy@xx.com'); ---insert into s2_chat_config (`id` ,`domain_id` ,`default_metrics`,`visibility`,`entity_info` ,`dictionary_info`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) values (1,1,'[{"metricId":1,"unit":7,"period":"DAY"}]','{"blackDimIdList":[],"blackMetricIdList":[]}','{"entityIds":[2],"names":["用户","用户姓名"],"detailData":{"dimensionIds":[1,2],"metricIds":[2]}}','[{"itemId":1,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":2,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":3,"type":"DIMENSION","blackList":[],"isDictInfo":true}]','2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); -insert into s2_chat_config (`id` ,`domain_id` ,`chat_detail_config`,`chat_agg_config`, `recommended_questions`, `created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) -values (1,1, - '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":2,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[1,2],"metricIds":[1],"unit":7,"period":"DAY"},"entity":null}', - '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":2,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[1,2],"metricIds":[1],"unit":7,"period":"DAY"}}', - '[{"question":"超音数访问次数"},{"question":"超音数访问人数"},{"question":"超音数按部门访问次数"}]', - '2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); -insert into s2_chat_config (`id` ,`domain_id` ,`chat_detail_config`,`chat_agg_config`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) -values (2,2, - '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":7,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[4,5,6,7],"metricIds":[4],"unit":7,"period":"DAY"},"entity":{"entityId":7,"names":["歌手","艺人"]}}', - '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":7,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[4,5,6,7],"metricIds":[4],"unit":7,"period":"DAY"}}', - '2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); - -insert into s2_plugin (id, `type`, `domain`, pattern, parse_mode, `name`, created_at, created_by, updated_at, updated_by, config) VALUES (1, 'WEB_PAGE', 1, '访问情况', 'EMBEDDING_RECALL', '访问情况', '2023-06-11 19:36:47', 'admin', '2023-06-21 15:26:46', 'admin', '{"params":{"487C128A":"2"}, "url":"www.test.com"}'); - -- sample models insert into s2_database (id, domain_id , `name`, description, `type` ,config ,created_at ,created_by ,updated_at ,updated_by) VALUES(1, 1, 'H2数据实例', '', 'h2', '{"password":"semantic","url":"jdbc:h2:mem:semantic;DATABASE_TO_UPPER=false","userName":"root"}', '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin'); insert into s2_database (id, domain_id , `name`, description, `type` ,config ,created_at ,created_by ,updated_at ,updated_by) VALUES(2, 2, 'H2数据实例', '', 'h2', '{"password":"semantic","url":"jdbc:h2:mem:semantic;DATABASE_TO_UPPER=false","userName":"root"}', '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin'); @@ -37,7 +22,7 @@ insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, descr insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(6, 2, 4, '风格', 'genre', '风格', 1, 2, 'categorical', NULL, 'genre', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(7, 2, 4, '歌手名', 'singer_name', '歌手名', 1, 2, 'categorical', NULL, 'singer_name', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org) VALUES(1, '超音数', 'supersonic', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin' ); -insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org) VALUES(2, '艺人库', 'singer', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin' ); +insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org, entity) VALUES(2, '艺人库', 'singer', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin','{"entityId": 7, "names": ["歌手", "艺人"]}' ); insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(1, 1, '停留时长', 'stay_hours', '停留时长', 1, 2, 'ATOMIC', '{"expr":"s2_stay_time_statis_stay_hours","measures":[{"agg":"sum","expr":"stay_hours","isCreateMetric":1,"datasourceId":1,"bizName":"s2_stay_time_statis_stay_hours","name":"s2_stay_time_statis_stay_hours"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(2, 1, '访问次数', 'pv', '访问次数', 1, 0, 'ATOMIC', ' {"expr":"s2_pv_uv_statis_pv","measures":[{"agg":"sum","bizName":"s2_pv_uv_statis_pv","datasourceId":2,"expr":"pv","isCreateMetric":1,"name":"s2_pv_uv_statis_pv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(3, 1, '访问人数', 'uv', '访问人数', 1, 0, 'ATOMIC', ' {"expr":"s2_pv_uv_statis_uv","measures":[{"agg":"count_distinct","bizName":"s2_pv_uv_statis_uv","datasourceId":2,"expr":"uv","isCreateMetric":1,"name":"s2_pv_uv_statis_uv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); diff --git a/launchers/standalone/src/main/resources/db/schema-h2.sql b/launchers/standalone/src/main/resources/db/schema-h2.sql index a5ffde8e6..a625909f8 100644 --- a/launchers/standalone/src/main/resources/db/schema-h2.sql +++ b/launchers/standalone/src/main/resources/db/schema-h2.sql @@ -55,40 +55,6 @@ CREATE TABLE IF NOT EXISTS `s2_chat_config` ( COMMENT ON TABLE s2_chat_config IS 'chat config information table '; - - -CREATE TABLE IF NOT EXISTS `s2_dictionary` ( - `id` INT NOT NULL AUTO_INCREMENT, - `domain_id` INT NOT NULL , - `dim_value_infos` LONGVARCHAR , -- dimension value setting information - `created_at` TIMESTAMP NOT NULL , - `updated_at` TIMESTAMP NOT NULL , - `created_by` varchar(100) NOT NULL , - `updated_by` varchar(100) DEFAULT NULL , - `status` INT NOT NULL DEFAULT '0' , -- domain extension information status : 0 is normal, 1 is off the shelf, 2 is deleted - PRIMARY KEY (`id`), - UNIQUE (domain_id) - ); -COMMENT ON TABLE s2_dictionary IS 'dictionary configuration information table'; - - -CREATE TABLE IF NOT EXISTS `s2_dictionary_task` ( - `id` INT NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL , -- task name - `description` varchar(255) , - `command`LONGVARCHAR NOT NULL , -- task Request Parameters - `command_md5` varchar(255) NOT NULL , -- task Request Parameters md5 - `status` INT NOT NULL , -- the final status of the task - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP , - `created_by` varchar(100) NOT NULL , - `progress` DOUBLE default 0.00 , -- task real-time progress - `elapsed_ms` bigINT DEFAULT NULL , -- the task takes time in milliseconds - `message` LONGVARCHAR , -- remark related information - PRIMARY KEY (`id`) - ); -COMMENT ON TABLE s2_dictionary_task IS 'dictionary task information table'; - - create table s2_user ( id INT AUTO_INCREMENT, @@ -321,12 +287,14 @@ CREATE TABLE IF NOT EXISTS `s2_plugin` `domain` varchar(100) NULL, `pattern` varchar(500) NULL, `parse_mode` varchar(100) NULL, + `parse_mode_config` LONGVARCHAR NULL, `name` varchar(100) NULL, `created_at` TIMESTAMP NULL, `created_by` varchar(100) null, `updated_at` TIMESTAMP NULL, `updated_by` varchar(100) NULL, `config` LONGVARCHAR NULL, + `comment` LONGVARCHAR NULL, PRIMARY KEY (`id`) ); COMMENT ON TABLE s2_plugin IS 'plugin information table'; diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/BaseQueryTest.java b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/BaseQueryTest.java index 0d03808da..3fe268d76 100644 --- a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/BaseQueryTest.java +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/BaseQueryTest.java @@ -1,19 +1,18 @@ package com.tencent.supersonic.integration; import com.tencent.supersonic.StandaloneLauncher; -import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.ChatContext; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.ExecuteQueryReq; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; +import com.tencent.supersonic.chat.api.pojo.response.ParseResp; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.api.pojo.response.QueryState; import com.tencent.supersonic.chat.service.ChatService; import com.tencent.supersonic.chat.service.ConfigService; import com.tencent.supersonic.chat.service.QueryService; -import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.util.DataUtils; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -22,13 +21,10 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import java.time.LocalDate; -import java.util.Comparator; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; @RunWith(SpringRunner.class) @SpringBootTest(classes = StandaloneLauncher.class) @@ -38,6 +34,7 @@ public class BaseQueryTest { protected final int unit = 7; protected final String startDay = LocalDate.now().plusDays(-unit).toString(); protected final String endDay = LocalDate.now().plusDays(-1).toString(); + protected final String period = "DAY"; @Autowired @Qualifier("chatQueryService") @@ -47,29 +44,39 @@ public class BaseQueryTest { @Autowired protected ConfigService configService; - protected Integer getNewChat(String chatName) { - chatService.addChat(User.getFakeUser(), chatName); - Optional chatId = chatService.getAll(User.getFakeUser().getName()).stream().map(c -> c.getChatId()).sorted(Comparator.reverseOrder()).findFirst(); - if (chatId.isPresent()) { - return chatId.get().intValue(); - } - return 1; - } - protected QueryResult submitMultiTurnChat(String queryText) throws Exception { - QueryRequest queryContextReq = DataUtils.getQueryContextReq(20, queryText); - return queryService.executeQuery(queryContextReq); + ParseResp parseResp = submitParse(queryText); + + ExecuteQueryReq request = new ExecuteQueryReq(); + request.setChatId(parseResp.getChatId()); + request.setQueryText(parseResp.getQueryText()); + request.setUser(DataUtils.getUser()); + request.setParseInfo(parseResp.getSelectedParses().get(0)); + + return queryService.performExecution(request); } protected QueryResult submitNewChat(String queryText) throws Exception { - chatService.addChat(User.getFakeUser(), RandomStringUtils.random(5)); + ParseResp parseResp = submitParse(queryText); - ChatContext chatContext = chatService.getOrCreateContext(10); + ExecuteQueryReq request = new ExecuteQueryReq(); + request.setChatId(parseResp.getChatId()); + request.setQueryText(parseResp.getQueryText()); + request.setUser(DataUtils.getUser()); + request.setParseInfo(parseResp.getSelectedParses().get(0)); + + QueryResult result = queryService.performExecution(request); + + ChatContext chatContext = chatService.getOrCreateContext(parseResp.getChatId()); chatContext.setParseInfo(new SemanticParseInfo()); chatService.updateContext(chatContext); - QueryRequest queryContextReq = DataUtils.getQueryContextReq(10, queryText); - return queryService.executeQuery(queryContextReq); + return result; + } + + protected ParseResp submitParse(String queryText) { + QueryReq queryContextReq = DataUtils.getQueryContextReq(10, queryText); + return queryService.performParsing(queryContextReq); } protected void assertSchemaElements(Set expected, Set actual) { @@ -81,17 +88,6 @@ public class BaseQueryTest { assertEquals(expectedNames, actualNames); } - protected void assertDateConf(DateConf expected, DateConf actual) { - Boolean timeFilterExist = expected.getStartDate().equals(actual.getStartDate()) - && expected.getEndDate().equals(actual.getEndDate()) - && expected.getDateMode().equals(actual.getDateMode()) - || expected.getUnit().equals(actual.getUnit()) && - expected.getDateMode().equals(actual.getDateMode()) && - expected.getPeriod().equals(actual.getPeriod()); - - assertTrue(timeFilterExist); - } - protected void assertQueryResult(QueryResult expected, QueryResult actual) { SemanticParseInfo expectedParseInfo = expected.getChatContext(); SemanticParseInfo actualParseInfo = actual.getChatContext(); @@ -106,7 +102,7 @@ public class BaseQueryTest { assertEquals(expectedParseInfo.getDimensionFilters(), actualParseInfo.getDimensionFilters()); assertEquals(expectedParseInfo.getMetricFilters(), actualParseInfo.getMetricFilters()); - assertDateConf(expectedParseInfo.getDateInfo(), actualParseInfo.getDateInfo()); + assertEquals(expectedParseInfo.getDateInfo(), actualParseInfo.getDateInfo()); } } diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/EntityQueryTest.java b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/EntityQueryTest.java index 8d0737a32..b522827f7 100644 --- a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/EntityQueryTest.java +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/EntityQueryTest.java @@ -1,23 +1,46 @@ package com.tencent.supersonic.integration; +import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.query.rule.entity.EntityFilterQuery; +import com.tencent.supersonic.chat.query.rule.metric.MetricEntityQuery; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; import com.tencent.supersonic.util.DataUtils; import org.junit.Test; - import java.util.ArrayList; import java.util.List; - import static com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum.NONE; public class EntityQueryTest extends BaseQueryTest { @Test - public void queryTest_ENTITY_LIST_FILTER()throws Exception { + public void queryTest_METRIC_ENTITY_QUERY() throws Exception { + QueryResult actualResult = submitNewChat("艺人周杰伦的播放量"); + + QueryResult expectedResult = new QueryResult(); + SemanticParseInfo expectedParseInfo = new SemanticParseInfo(); + expectedResult.setChatContext(expectedParseInfo); + + expectedResult.setQueryMode(MetricEntityQuery.QUERY_MODE); + expectedParseInfo.setAggType(NONE); + + QueryFilter dimensionFilter = DataUtils.getFilter("singer_name", FilterOperatorEnum.EQUALS, "周杰伦", "歌手名", 7L); + expectedParseInfo.getDimensionFilters().add(dimensionFilter); + + SchemaElement metric = SchemaElement.builder().name("播放量").build(); + expectedParseInfo.getMetrics().add(metric); + + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, 7, period, startDay, endDay)); + expectedParseInfo.setNativeQuery(false); + + assertQueryResult(expectedResult, actualResult); + } + + @Test + public void queryTest_ENTITY_LIST_FILTER() throws Exception { QueryResult actualResult = submitNewChat("爱情、流行类型的艺人"); QueryResult expectedResult = new QueryResult(); @@ -33,7 +56,19 @@ public class EntityQueryTest extends BaseQueryTest { QueryFilter dimensionFilter = DataUtils.getFilter("genre", FilterOperatorEnum.IN, list, "风格", 6L); expectedParseInfo.getDimensionFilters().add(dimensionFilter); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(1, DateConf.DateMode.RECENT_UNITS, "DAY")); + SchemaElement metric = SchemaElement.builder().name("播放量").build(); + expectedParseInfo.getMetrics().add(metric); + + SchemaElement dim1 = SchemaElement.builder().name("歌手名").build(); + SchemaElement dim2 = SchemaElement.builder().name("活跃区域").build(); + SchemaElement dim3 = SchemaElement.builder().name("风格").build(); + SchemaElement dim4 = SchemaElement.builder().name("代表作").build(); + expectedParseInfo.getDimensions().add(dim1); + expectedParseInfo.getDimensions().add(dim2); + expectedParseInfo.getDimensions().add(dim3); + expectedParseInfo.getDimensions().add(dim4); + + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN, startDay, startDay)); expectedParseInfo.setNativeQuery(true); assertQueryResult(expectedResult, actualResult); diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MetricQueryTest.java b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MetricQueryTest.java index 611e035fc..86af90a9d 100644 --- a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MetricQueryTest.java +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MetricQueryTest.java @@ -4,9 +4,9 @@ import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; -import com.tencent.supersonic.chat.config.ChatConfigEditReqReq; -import com.tencent.supersonic.chat.config.ChatConfigResp; -import com.tencent.supersonic.chat.config.ItemVisibility; +import com.tencent.supersonic.chat.api.pojo.request.ChatConfigEditReqReq; +import com.tencent.supersonic.chat.api.pojo.response.ChatConfigResp; +import com.tencent.supersonic.chat.api.pojo.request.ItemVisibility; import com.tencent.supersonic.chat.query.rule.metric.MetricDomainQuery; import com.tencent.supersonic.chat.query.rule.metric.MetricFilterQuery; import com.tencent.supersonic.chat.query.rule.metric.MetricGroupByQuery; @@ -42,7 +42,7 @@ public class MetricQueryTest extends BaseQueryTest { expectedParseInfo.getDimensionFilters().add(DataUtils.getFilter("user_name", FilterOperatorEnum.EQUALS, "alice", "用户名", 2L)); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -61,7 +61,7 @@ public class MetricQueryTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("访问次数")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -81,7 +81,7 @@ public class MetricQueryTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("访问次数")); expectedParseInfo.getDimensions().add(DataUtils.getSchemaElement("部门")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -106,7 +106,7 @@ public class MetricQueryTest extends BaseQueryTest { QueryFilter dimensionFilter = DataUtils.getFilter("user_name", FilterOperatorEnum.IN, list, "用户名", 2L); expectedParseInfo.getDimensionFilters().add(dimensionFilter); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -126,7 +126,7 @@ public class MetricQueryTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("访问次数")); expectedParseInfo.getDimensions().add(DataUtils.getSchemaElement("用户名")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(3, DateConf.DateMode.RECENT_UNITS, "DAY")); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(3, DateConf.DateMode.RECENT, "DAY")); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -145,7 +145,7 @@ public class MetricQueryTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("访问次数")); expectedParseInfo.getDimensions().add(DataUtils.getSchemaElement("部门")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -157,7 +157,7 @@ public class MetricQueryTest extends BaseQueryTest { DateFormat textFormat = new SimpleDateFormat("yyyy年mm月dd日"); String dateStr = textFormat.format(format.parse(startDay)); - QueryResult actualResult = submitNewChat(String.format("想知道{}alice的访问次数", dateStr)); + QueryResult actualResult = submitNewChat(String.format("想知道%salice的访问次数", dateStr)); QueryResult expectedResult = new QueryResult(); SemanticParseInfo expectedParseInfo = new SemanticParseInfo(); @@ -171,7 +171,7 @@ public class MetricQueryTest extends BaseQueryTest { expectedParseInfo.getDimensionFilters().add(DataUtils.getFilter("user_name", FilterOperatorEnum.EQUALS, "alice", "用户名", 2L)); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, startDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN, 1, period, startDay, startDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -198,7 +198,7 @@ public class MetricQueryTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("访问次数")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MultiTurnsTest.java b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MultiTurnsTest.java index 7902a015d..9488da4a7 100644 --- a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MultiTurnsTest.java +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/MultiTurnsTest.java @@ -34,7 +34,7 @@ public class MultiTurnsTest extends BaseQueryTest { expectedParseInfo.getDimensionFilters().add(DataUtils.getFilter("user_name", FilterOperatorEnum.EQUALS, "alice", "用户名", 2L)); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -57,7 +57,7 @@ public class MultiTurnsTest extends BaseQueryTest { expectedParseInfo.getDimensionFilters().add(DataUtils.getFilter("user_name", FilterOperatorEnum.EQUALS, "alice", "用户名", 2L)); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -80,7 +80,7 @@ public class MultiTurnsTest extends BaseQueryTest { expectedParseInfo.getDimensionFilters().add(DataUtils.getFilter("user_name", FilterOperatorEnum.EQUALS, "lucy", "用户名", 2L)); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -101,7 +101,7 @@ public class MultiTurnsTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("停留时长")); expectedParseInfo.getDimensions().add(DataUtils.getSchemaElement("部门")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, endDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.RECENT, unit, period, startDay, endDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -124,7 +124,7 @@ public class MultiTurnsTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("停留时长")); expectedParseInfo.getDimensions().add(DataUtils.getSchemaElement("部门")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN_CONTINUOUS, startDay, startDay)); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(DateConf.DateMode.BETWEEN, 1, period, startDay, startDay)); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); @@ -145,7 +145,7 @@ public class MultiTurnsTest extends BaseQueryTest { expectedParseInfo.getMetrics().add(DataUtils.getSchemaElement("停留时长")); expectedParseInfo.getDimensions().add(DataUtils.getSchemaElement("部门")); - expectedParseInfo.setDateInfo(DataUtils.getDateConf(30, DateConf.DateMode.RECENT_UNITS, "DAY")); + expectedParseInfo.setDateInfo(DataUtils.getDateConf(30, DateConf.DateMode.RECENT, "DAY")); expectedParseInfo.setNativeQuery(false); assertQueryResult(expectedResult, actualResult); diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/BasePluginTest.java b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/BasePluginTest.java index bb0e00d19..7c0f02508 100644 --- a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/BasePluginTest.java +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/BasePluginTest.java @@ -3,7 +3,7 @@ package com.tencent.supersonic.integration.plugin; import com.tencent.supersonic.StandaloneLauncher; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.api.pojo.response.QueryState; -import com.tencent.supersonic.chat.query.plugin.WebBase; +import com.tencent.supersonic.chat.query.plugin.WebBaseResult; import com.tencent.supersonic.chat.query.plugin.webpage.WebPageQuery; import com.tencent.supersonic.chat.query.plugin.webpage.WebPageResponse; import lombok.extern.slf4j.Slf4j; @@ -25,10 +25,10 @@ public class BasePluginTest { Assert.assertEquals(queryResult.getQueryState(), QueryState.SUCCESS); Assert.assertEquals(queryResult.getQueryMode(), WebPageQuery.QUERY_MODE); WebPageResponse webPageResponse = (WebPageResponse) queryResult.getResponse(); - WebBase webPage = webPageResponse.getWebPage(); + WebBaseResult webPage = webPageResponse.getWebPage(); Assert.assertEquals(webPage.getUrl(), "www.test.com"); - Map valueParams = webPage.getValueParams(); - Assert.assertEquals(valueParams.get("name"), "alice"); + Assert.assertEquals(1, webPage.getParams().size()); + Assert.assertEquals("alice", webPage.getParams().get(0).getValue()); } } \ No newline at end of file diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/PluginRecognizeTest.java b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/PluginRecognizeTest.java index bb6b8394c..8e231a187 100644 --- a/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/PluginRecognizeTest.java +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/integration/plugin/PluginRecognizeTest.java @@ -2,7 +2,7 @@ package com.tencent.supersonic.integration.plugin; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; import com.tencent.supersonic.chat.api.pojo.request.QueryFilters; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.chat.api.pojo.response.QueryResult; import com.tencent.supersonic.chat.parser.embedding.EmbeddingConfig; import com.tencent.supersonic.chat.plugin.PluginManager; @@ -29,7 +29,7 @@ public class PluginRecognizeTest extends BasePluginTest{ public void webPageRecognize() throws Exception { PluginMockConfiguration.mockEmbeddingRecognize(pluginManager, "最近的访问情况怎么样","1"); PluginMockConfiguration.mockEmbeddingUrl(embeddingConfig); - QueryRequest queryContextReq = DataUtils.getQueryContextReq(1000, "alice最近的访问情况怎么样"); + QueryReq queryContextReq = DataUtils.getQueryContextReq(1000, "alice最近的访问情况怎么样"); QueryResult queryResult = queryService.executeQuery(queryContextReq); assertPluginRecognizeResult(queryResult); } @@ -38,11 +38,12 @@ public class PluginRecognizeTest extends BasePluginTest{ public void webPageRecognizeWithQueryFilter() throws Exception { PluginMockConfiguration.mockEmbeddingRecognize(pluginManager, "在超音数最近的情况怎么样","1"); PluginMockConfiguration.mockEmbeddingUrl(embeddingConfig); - QueryRequest queryRequest = DataUtils.getQueryContextReq(1000, "在超音数最近的情况怎么样"); + QueryReq queryRequest = DataUtils.getQueryContextReq(1000, "在超音数最近的情况怎么样"); QueryFilters queryFilters = new QueryFilters(); QueryFilter queryFilter = new QueryFilter(); queryFilter.setElementID(2L); queryFilter.setValue("alice"); + queryRequest.setDomainId(1L); queryFilters.getFilters().add(queryFilter); queryRequest.setQueryFilters(queryFilters); QueryResult queryResult = queryService.executeQuery(queryRequest); diff --git a/launchers/standalone/src/test/java/com/tencent/supersonic/util/DataUtils.java b/launchers/standalone/src/test/java/com/tencent/supersonic/util/DataUtils.java index 7269133f0..22f8a96cc 100644 --- a/launchers/standalone/src/test/java/com/tencent/supersonic/util/DataUtils.java +++ b/launchers/standalone/src/test/java/com/tencent/supersonic/util/DataUtils.java @@ -4,19 +4,27 @@ import com.tencent.supersonic.auth.api.authentication.pojo.User; import com.tencent.supersonic.chat.api.pojo.SchemaElement; import com.tencent.supersonic.chat.api.pojo.SchemaElementType; import com.tencent.supersonic.chat.api.pojo.request.QueryFilter; -import com.tencent.supersonic.chat.api.pojo.request.QueryRequest; +import com.tencent.supersonic.chat.api.pojo.request.QueryReq; import com.tencent.supersonic.common.pojo.DateConf; import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum; import java.util.Set; +import static java.time.LocalDate.now; + public class DataUtils { - public static QueryRequest getQueryContextReq(Integer id, String query) { - QueryRequest queryContextReq = new QueryRequest(); + private static final User user_test = new User(1L, "admin", "admin", "admin@email"); + + public static User getUser() { + return user_test; + } + + public static QueryReq getQueryContextReq(Integer id, String query) { + QueryReq queryContextReq = new QueryReq(); queryContextReq.setQueryText(query);//"alice的访问次数" queryContextReq.setChatId(id); - queryContextReq.setUser(new User(1L, "admin", "admin", "admin@email")); + queryContextReq.setUser(user_test); return queryContextReq; } @@ -64,6 +72,18 @@ public class DataUtils { dateInfo.setUnit(unit); dateInfo.setDateMode(dateMode); dateInfo.setPeriod(period); + dateInfo.setStartDate(now().plusDays(-unit).toString()); + dateInfo.setEndDate(now().toString()); + return dateInfo; + } + + public static DateConf getDateConf(DateConf.DateMode dateMode, Integer unit, String period, String startDate, String endDate) { + DateConf dateInfo = new DateConf(); + dateInfo.setUnit(unit); + dateInfo.setDateMode(dateMode); + dateInfo.setPeriod(period); + dateInfo.setStartDate(startDate); + dateInfo.setEndDate(endDate); return dateInfo; } diff --git a/launchers/standalone/src/test/resources/META-INF/spring.factories b/launchers/standalone/src/test/resources/META-INF/spring.factories index c9a823edb..41d5b4236 100644 --- a/launchers/standalone/src/test/resources/META-INF/spring.factories +++ b/launchers/standalone/src/test/resources/META-INF/spring.factories @@ -20,5 +20,5 @@ com.tencent.supersonic.chat.query.QuerySelector=\ com.tencent.supersonic.chat.application.query.DomainResolver=\ com.tencent.supersonic.chat.application.query.HeuristicDomainResolver -com.tencent.supersonic.auth.authentication.domain.interceptor.AuthenticationInterceptor=\ - com.tencent.supersonic.auth.authentication.domain.interceptor.DefaultAuthenticationInterceptor +com.tencent.supersonic.auth.authentication.interceptor.AuthenticationInterceptor=\ + com.tencent.supersonic.auth.authentication.interceptor.DefaultAuthenticationInterceptor diff --git a/launchers/standalone/src/test/resources/db/data-h2.sql b/launchers/standalone/src/test/resources/db/data-h2.sql index 2c9f9b80f..502793606 100644 --- a/launchers/standalone/src/test/resources/db/data-h2.sql +++ b/launchers/standalone/src/test/resources/db/data-h2.sql @@ -5,14 +5,14 @@ insert into s2_user (id, `name`, password, display_name, email) values (3, 'tom' insert into s2_user (id, `name`, password, display_name, email) values (4, 'lucy','123456','lucy','lucy@xx.com'); --insert into s2_chat_config (`id` ,`domain_id` ,`default_metrics`,`visibility`,`entity_info` ,`dictionary_info`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) values (1,1,'[{"metricId":1,"unit":7,"period":"DAY"}]','{"blackDimIdList":[],"blackMetricIdList":[]}','{"entityIds":[2],"names":["用户","用户姓名"],"detailData":{"dimensionIds":[1,2],"metricIds":[2]}}','[{"itemId":1,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":2,"type":"DIMENSION","blackList":[],"isDictInfo":true},{"itemId":3,"type":"DIMENSION","blackList":[],"isDictInfo":true}]','2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); -insert into s2_chat_config (`id` ,`domain_id` ,`chat_detail_config`,`chat_agg_config`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) -values (1,1,'{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":2,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[1,2],"metricIds":[1],"unit":7,"period":"DAY"},"entity":null}', - '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":2,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[1,2],"metricIds":[1],"ratioMetricIds":[2],"unit":7,"period":"DAY"}}', - '2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); -insert into s2_chat_config (`id` ,`domain_id` ,`chat_detail_config`,`chat_agg_config`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) -values (2,2,'{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":7,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[4,5,6,7],"metricIds":[4],"unit":7,"period":"DAY"},"entity":{"entityId":1,"names":["歌手","艺人"]}}', - '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":7,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[4,5,6,7],"metricIds":[4],"unit":7,"period":"DAY"}}', - '2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); +-- insert into s2_chat_config (`id` ,`domain_id` ,`chat_detail_config`,`chat_agg_config`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) +-- values (1,1,'{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":2,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[1,2],"metricIds":[1],"unit":7,"period":"DAY"}}', +-- '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":2,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[1,2],"metricIds":[1],"ratioMetricIds":[2],"unit":7,"period":"DAY","timeMode":"RECENT"}}', +-- '2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); +-- insert into s2_chat_config (`id` ,`domain_id` ,`chat_detail_config`,`chat_agg_config`,`created_at`,`updated_at`,`created_by`,`updated_by`,`status` ) +-- values (2,2,'{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":7,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[4,5,6,7],"metricIds":[4],"unit":7,"period":"DAY"}}', +-- '{"visibility":{"blackDimIdList":[],"blackMetricIdList":[]},"knowledgeInfos":[{"itemId":7,"type":"DIMENSION","searchEnable":true}],"chatDefaultConfig":{"dimensionIds":[4,5,6,7],"metricIds":[4],"unit":7,"period":"DAY","timeMode":"RECENT"}}', +-- '2023-05-24 18:00:00','2023-05-25 11:00:00','admin','admin',1); insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (1, '超音数访问统计','2023-06-10 10:00:52.495','2023-06-10 10:00:52','admin','您好,欢迎使用内容智能小Q','0','0'); insert into s2_chat (chat_id, `chat_name`, create_time, last_time, creator,last_question,is_delete,is_top) values (2, '用户访问统计','2023-06-10 10:01:04.528','2023-06-10 10:01:04','admin','您好,欢迎使用内容智能小Q','0','0'); @@ -27,8 +27,8 @@ insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`, insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(5, '2023-06-10 10:41:48.211','停留时长','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); insert into s2_chat_query (`question_id`,`create_time`,`query_text`,`user_name`,`query_state`,`chat_id`,`query_response`,`score`,`feedback`) VALUES(6, '2023-06-10 10:42:02.184','访问','admin',0,2,'{"queryMode":"METRIC_FILTER","querySql":"SELECT `sys_imp_date` , `stay_hours` FROM ( SELECT `sys_imp_date` , `s2_stay_time_statis_stay_hours` AS `stay_hours` FROM ( SELECT SUM ( `s2_stay_time_statis_stay_hours` ) AS `s2_stay_time_statis_stay_hours` , `sys_imp_date` FROM ( SELECT `user_name` , `stay_hours` AS `s2_stay_time_statis_stay_hours` , `imp_date` AS `sys_imp_date` FROM ( SELECT `imp_date` , `page` , `user_name` , `stay_hours` FROM `s2_stay_time_statis` ) AS `s2_stay_time_statis` ) AS `src00_s2_stay_time_statis_df18` WHERE ( `sys_imp_date` >= ''2023-06-03'' AND `sys_imp_date` <= ''2023-06-09'' AND `user_name` = ''alice'' ) GROUP BY `sys_imp_date` ) AS `s2_stay_time_statis_0` ) AS `s2_stay_time_statis_1` LIMIT 10","queryState":0,"queryColumns":[{"name":"date","type":"VARCHAR","nameEn":"sys_imp_date","showType":"DATE","authorized":true},{"name":"停留时长","type":"DOUBLE","nameEn":"stay_hours","showType":"NUMBER","authorized":true}],"entityInfo":{"domainInfo":{"itemId":1,"name":"超音数","bizName":"supersonic","words":["用户","用户姓名"],"primaryEntityBizName":"user_name"},"dimensions":[{"itemId":1,"name":"部门","bizName":"department","value":"sales"},{"itemId":2,"name":"用户名","bizName":"user_name","value":"alice"}],"metrics":[{"itemId":2,"name":"访问次数","bizName":"pv","value":"2"}],"entityId":"alice"},"chatContext":{"queryMode":"METRIC_FILTER","domainId":1,"domainName":"超音数","entity":0,"metrics":[{"id":1,"name":"停留时长","bizName":"stay_hours","status":1,"sensitiveLevel":0}],"dimensions":[{"bizName":"sys_imp_date","status":1,"sensitiveLevel":0}],"dimensionFilters":[{"bizName":"user_name","name":"用户名","operator":"=","value":"alice","elementID":2}],"metricFilters":[],"orders":[],"dateInfo":{"dateMode":"RECENT_UNITS","startDate":"2023-06-03","endDate":"2023-06-09","dateList":[],"unit":7,"period":"DAY"},"limit":10,"nativeQuery":false},"queryResults":[{"sys_imp_date":"2023-06-03","stay_hours":0.5963801306980994},{"sys_imp_date":"2023-06-04","stay_hours":1.5120376931855422},{"sys_imp_date":"2023-06-06","stay_hours":3.7790223355266317},{"sys_imp_date":"2023-06-07","stay_hours":0.8654528466186735},{"sys_imp_date":"2023-06-08","stay_hours":0.9796159603778489},{"sys_imp_date":"2023-06-09","stay_hours":0.6705580511822682}]}',0,''); -insert into s2_plugin (id, `type`, `domain`, pattern, parse_mode, `name`, created_at, created_by, updated_at, updated_by, config) VALUES (1, 'WEB_PAGE', 1, '访问情况', 'EMBEDDING_RECALL', '访问情况', '2023-06-11 19:36:47', 'admin', '2023-06-21 15:26:46', 'admin', '{"params":{"name":"2"}, "url":"www.test.com"}'); -insert into s2_plugin (id, `type`, `domain`, pattern, parse_mode, `name`, created_at, created_by, updated_at, updated_by, config) VALUES (2, 'WEB_PAGE', 2, '播放表现', 'EMBEDDING_RECALL', '播放表现', '2023-06-11 19:36:47', 'admin', '2023-06-21 15:26:46', 'admin', '{"params":{"name":"7"}, "url":"www.test.com"}'); +-- insert into s2_plugin (id, `type`, `domain`, pattern, parse_mode, parse_mode_config, `name`, created_at, created_by, updated_at, updated_by, config) VALUES (1, 'WEB_PAGE', 1, '访问情况', 'EMBEDDING_RECALL', null, '访问情况', '2023-06-11 19:36:47', 'admin', '2023-06-21 15:26:46', 'admin', '{"params":{"1":[{"key":"name","paramType":"SEMANTIC","elementId":2}]}, "url":"www.test.com"}'); +-- insert into s2_plugin (id, `type`, `domain`, pattern, parse_mode, parse_mode_config, `name`, created_at, created_by, updated_at, updated_by, config) VALUES (2, 'DSL', null, '', 'FUNCTION_CALL', null, '访问情况', '2023-06-11 19:36:47', 'admin', '2023-06-21 15:26:46', 'admin', ''); -- semantic data insert into s2_database (id, domain_id , `name`, description, `type` ,config ,created_at ,created_by ,updated_at ,updated_by) VALUES(1, 1, 'H2数据实例', '', 'h2', '{"password":"semantic","url":"jdbc:h2:mem:semantic;DATABASE_TO_UPPER=false","userName":"root"}', '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin'); @@ -48,7 +48,8 @@ insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, descr insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(6, 2, 4, '风格', 'genre', '风格', 1, 2, 'categorical', NULL, 'genre', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); insert into s2_dimension (id , domain_id, datasource_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, expr, created_at, created_by, updated_at, updated_by, semantic_type) VALUES(7, 2, 4, '歌手名', 'singer_name', '歌手名', 1, 2, 'categorical', NULL, 'singer_name', '2023-05-24 00:00:00', 'admin', '2023-05-25 00:00:00', 'admin', 'CATEGORY'); insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org) VALUES(1, '超音数', 'supersonic', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin' ); -insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org) VALUES(2, '艺人库', 'singer', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin' ); +-- insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org) VALUES(2, '艺人库', 'singer', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin' ); +insert into s2_domain (id, `name`, biz_name, parent_id, status, created_at, created_by, updated_at, updated_by, `admin`, admin_org, is_open, viewer, view_org, entity) VALUES(2, '艺人库', 'singer', 0, 1, '2023-05-24 00:00:00', 'admin', '2023-05-24 00:00:00', 'admin', 'admin', '', 0, 'admin,tom,jack', 'admin','{"entityId": 7, "names": ["歌手", "艺人"]}' ); insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(1, 1, '停留时长', 'stay_hours', '停留时长', 1, 2, 'expr', '{"expr":"s2_stay_time_statis_stay_hours","measures":[{"agg":"sum","expr":"stay_hours","isCreateMetric":1,"datasourceId":1,"bizName":"s2_stay_time_statis_stay_hours","name":"s2_stay_time_statis_stay_hours"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(2, 1, '访问次数', 'pv', '访问次数', 1, 0, 'expr', ' {"expr":"s2_pv_uv_statis_pv","measures":[{"agg":"sum","bizName":"s2_pv_uv_statis_pv","datasourceId":2,"expr":"pv","isCreateMetric":1,"name":"s2_pv_uv_statis_pv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); insert into s2_metric (id, domain_id, `name`, biz_name, description, status, sensitive_level, `type`, type_params, created_at, created_by, updated_at, updated_by, data_format_type, data_format) VALUES(3, 1, '访问人数', 'uv', '访问人数', 1, 0, 'expr', ' {"expr":"s2_pv_uv_statis_uv","measures":[{"agg":"count_distinct","bizName":"s2_pv_uv_statis_uv","datasourceId":2,"expr":"uv","isCreateMetric":1,"name":"s2_pv_uv_statis_uv"}]}' , '2023-05-24 17:00:00', 'admin', '2023-05-25 00:00:00', 'admin', NULL, NULL ); diff --git a/launchers/standalone/src/test/resources/db/schema-h2.sql b/launchers/standalone/src/test/resources/db/schema-h2.sql index ba2b35a7b..18d01dca8 100644 --- a/launchers/standalone/src/test/resources/db/schema-h2.sql +++ b/launchers/standalone/src/test/resources/db/schema-h2.sql @@ -320,12 +320,14 @@ CREATE TABLE IF NOT EXISTS `s2_plugin` `domain` varchar(100) NULL, `pattern` varchar(500) NULL, `parse_mode` varchar(100) NULL, + `parse_mode_config` LONGVARCHAR NULL, `name` varchar(100) NULL, `created_at` TIMESTAMP NULL, `created_by` varchar(100) null, `updated_at` TIMESTAMP NULL, `updated_by` varchar(100) NULL, `config` LONGVARCHAR NULL, + `comment` LONGVARCHAR NULL, PRIMARY KEY (`id`) ); COMMENT ON TABLE s2_plugin IS 'plugin information table'; diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/request/PageSchemaItemReq.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/request/PageSchemaItemReq.java index de2ace366..d3ce23d20 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/request/PageSchemaItemReq.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/request/PageSchemaItemReq.java @@ -2,6 +2,7 @@ package com.tencent.supersonic.semantic.api.model.request; import com.tencent.supersonic.common.pojo.PageBaseReq; import lombok.Data; +import java.util.List; @Data public class PageSchemaItemReq extends PageBaseReq { @@ -10,7 +11,7 @@ public class PageSchemaItemReq extends PageBaseReq { private String name; private String bizName; private String createdBy; - private Long domainId; + private List domainIds; private Integer sensitiveLevel; private Integer status; } diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/response/DomainSchemaResp.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/response/DomainSchemaResp.java index 00ee2dcca..a57be2e5b 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/response/DomainSchemaResp.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/model/response/DomainSchemaResp.java @@ -1,6 +1,7 @@ package com.tencent.supersonic.semantic.api.model.response; import java.util.List; + import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -10,7 +11,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class DomainSchemaResp extends DomainResp { - private List entityNames; private List metrics; private List dimensions; diff --git a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/request/MetricReq.java b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/request/MetricReq.java index 17016ef22..5e493a1aa 100644 --- a/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/request/MetricReq.java +++ b/semantic/api/src/main/java/com/tencent/supersonic/semantic/api/query/request/MetricReq.java @@ -15,5 +15,6 @@ public class MetricReq { private String where; private Long limit; private List order; + private boolean nativeQuery = false; } diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DimensionServiceImpl.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DimensionServiceImpl.java index 5d06e641e..73c22a320 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DimensionServiceImpl.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DimensionServiceImpl.java @@ -109,6 +109,7 @@ public class DimensionServiceImpl implements DimensionService { public PageInfo queryDimension(PageDimensionReq pageDimensionReq) { DimensionFilter dimensionFilter = new DimensionFilter(); BeanUtils.copyProperties(pageDimensionReq, dimensionFilter); + dimensionFilter.setDomainIds(pageDimensionReq.getDomainIds()); PageInfo dimensionDOPageInfo = PageHelper.startPage(pageDimensionReq.getCurrent(), pageDimensionReq.getPageSize()) .doSelectPageInfo(() -> queryDimension(dimensionFilter)); diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DomainServiceImpl.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DomainServiceImpl.java index f8fa1d773..41353d46a 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DomainServiceImpl.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/DomainServiceImpl.java @@ -2,7 +2,9 @@ package com.tencent.supersonic.semantic.model.application; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.tencent.supersonic.auth.api.authentication.pojo.User; +import com.tencent.supersonic.auth.api.authentication.service.UserService; import com.tencent.supersonic.common.util.BeanMapper; import com.tencent.supersonic.common.util.JsonUtil; import com.tencent.supersonic.semantic.api.model.request.DomainReq; @@ -24,12 +26,7 @@ import com.tencent.supersonic.semantic.model.domain.pojo.Domain; import com.tencent.supersonic.semantic.model.domain.repository.DomainRepository; import com.tencent.supersonic.semantic.model.domain.utils.DomainConvert; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -47,14 +44,17 @@ public class DomainServiceImpl implements DomainService { private final MetricService metricService; private final DimensionService dimensionService; private final DatasourceService datasourceService; + private final UserService userService; public DomainServiceImpl(DomainRepository domainRepository, @Lazy MetricService metricService, - @Lazy DimensionService dimensionService, @Lazy DatasourceService datasourceService) { + @Lazy DimensionService dimensionService, @Lazy DatasourceService datasourceService, + UserService userService) { this.domainRepository = domainRepository; this.metricService = metricService; this.dimensionService = dimensionService; this.datasourceService = datasourceService; + this.userService = userService; } @@ -130,7 +130,7 @@ public class DomainServiceImpl implements DomainService { @Override public List getDomainListForAdmin(String userName) { List domainDOS = domainRepository.getDomainList(); - List orgIds = Lists.newArrayList(); + Set orgIds = Sets.newHashSet(); log.info("orgIds:{},userName:{}", orgIds, userName); Map> metricDomainMap = metricService.getMetrics().stream() .collect(Collectors.groupingBy(MetricResp::getDomainId)); @@ -144,7 +144,7 @@ public class DomainServiceImpl implements DomainService { @Override public List getDomainListForViewer(String userName) { List domainDOS = domainRepository.getDomainList(); - List orgIds = Lists.newArrayList(); + Set orgIds = Sets.newHashSet(); log.info("orgIds:{},userName:{}", orgIds, userName); return convertList(domainDOS, new HashMap<>(), new HashMap<>()).stream() .filter(domainDesc -> checkViewerPermission(orgIds, userName, domainDesc)) @@ -165,7 +165,7 @@ public class DomainServiceImpl implements DomainService { return ""; } Map map = getDomainFullPathMap(); - return map.containsKey(domainId) ? map.get(domainId) : ""; + return map.getOrDefault(domainId, ""); } @Override @@ -201,6 +201,32 @@ public class DomainServiceImpl implements DomainService { return getDomainList().stream().collect(Collectors.toMap(DomainResp::getId, a -> a, (k1, k2) -> k1)); } + @Override + public Set getDomainChildren(List domainIds) { + Set childDomains = new HashSet<>(); + if (CollectionUtils.isEmpty(domainIds)) { + return childDomains; + } + Map allDomainMap = getDomainMap(); + for (Long domainId : domainIds) { + DomainResp domain = allDomainMap.get(domainId); + if (domain != null) { + childDomains.add(domain); + Queue queue = new LinkedList<>(); + queue.add(domain); + while (!queue.isEmpty()) { + DomainResp currentDomain = queue.poll(); + for (DomainResp child : allDomainMap.values()) { + if (Objects.equals(child.getParentId(), currentDomain.getId())) { + childDomains.add(child); + queue.add(child); + } + } + } + } + } + return childDomains; + } public Map getDomainFullPathMap() { Map domainFullPathMap = new HashMap<>(); @@ -286,13 +312,16 @@ public class DomainServiceImpl implements DomainService { } - private boolean checkAdminPermission(List orgIds, String userName, DomainResp domainDesc) { + private boolean checkAdminPermission(Set orgIds, String userName, DomainResp domainDesc) { List admins = domainDesc.getAdmins(); List adminOrgs = domainDesc.getAdminOrgs(); if (admins.contains(userName) || domainDesc.getCreatedBy().equals(userName)) { return true; } + if (CollectionUtils.isEmpty(adminOrgs)) { + return false; + } for (String orgId : orgIds) { if (adminOrgs.contains(orgId)) { return true; @@ -301,7 +330,7 @@ public class DomainServiceImpl implements DomainService { return false; } - private boolean checkViewerPermission(List orgIds, String userName, DomainResp domainDesc) { + private boolean checkViewerPermission(Set orgIds, String userName, DomainResp domainDesc) { if (domainDesc.getIsOpen() == 1) { return true; } @@ -312,6 +341,9 @@ public class DomainServiceImpl implements DomainService { if (admins.contains(userName) || viewers.contains(userName) || domainDesc.getCreatedBy().equals(userName)) { return true; } + if (CollectionUtils.isEmpty(adminOrgs) && CollectionUtils.isEmpty(viewOrgs)) { + return false; + } for (String orgId : orgIds) { if (adminOrgs.contains(orgId)) { return true; diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/MetricServiceImpl.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/MetricServiceImpl.java index 3d12e030a..56f1289fd 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/MetricServiceImpl.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/application/MetricServiceImpl.java @@ -102,6 +102,9 @@ public class MetricServiceImpl implements MetricService { public PageInfo queryMetric(PageMetricReq pageMetricReq) { MetricFilter metricFilter = new MetricFilter(); BeanUtils.copyProperties(pageMetricReq, metricFilter); + Set domainResps = domainService.getDomainChildren(pageMetricReq.getDomainIds()); + List domainIds = domainResps.stream().map(DomainResp::getId).collect(Collectors.toList()); + metricFilter.setDomainIds(domainIds); PageInfo metricDOPageInfo = PageHelper.startPage(pageMetricReq.getCurrent(), pageMetricReq.getPageSize()) .doSelectPageInfo(() -> queryMetric(metricFilter)); diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/DomainService.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/DomainService.java index e466d0ff8..72415ed79 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/DomainService.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/DomainService.java @@ -8,6 +8,7 @@ import com.tencent.supersonic.semantic.api.model.response.DomainResp; import com.tencent.supersonic.semantic.api.model.response.DomainSchemaResp; import java.util.List; import java.util.Map; +import java.util.Set; public interface DomainService { @@ -36,6 +37,8 @@ public interface DomainService { List getDomainListForViewer(String userName); + Set getDomainChildren(List domainId); + List fetchDomainSchema(DomainSchemaFilterReq filter, User user); } diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/pojo/MetaFilter.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/pojo/MetaFilter.java index 1771eb67b..b33e18e18 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/pojo/MetaFilter.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/domain/pojo/MetaFilter.java @@ -1,6 +1,7 @@ package com.tencent.supersonic.semantic.model.domain.pojo; import lombok.Data; +import java.util.List; @Data @@ -14,7 +15,7 @@ public class MetaFilter { private String createdBy; - private Long domainId; + private List domainIds; private Integer sensitiveLevel; diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/mapper/MetricDOCustomMapper.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/mapper/MetricDOCustomMapper.java index 62845f58d..b51d3b404 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/mapper/MetricDOCustomMapper.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/mapper/MetricDOCustomMapper.java @@ -4,6 +4,7 @@ package com.tencent.supersonic.semantic.model.infrastructure.mapper; import com.tencent.supersonic.semantic.model.domain.dataobject.MetricDO; import java.util.List; +import com.tencent.supersonic.semantic.model.domain.pojo.MetricFilter; import org.apache.ibatis.annotations.Mapper; @@ -14,4 +15,6 @@ public interface MetricDOCustomMapper { void batchUpdate(List metricDOS); + List query(MetricFilter metricFilter); + } diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/DimensionRepositoryImpl.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/DimensionRepositoryImpl.java index 1d92f0299..4fa484720 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/DimensionRepositoryImpl.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/DimensionRepositoryImpl.java @@ -7,6 +7,7 @@ import com.tencent.supersonic.semantic.model.domain.pojo.DimensionFilter; import com.tencent.supersonic.semantic.model.infrastructure.mapper.DimensionDOCustomMapper; import com.tencent.supersonic.semantic.model.infrastructure.mapper.DimensionDOMapper; import java.util.List; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; @@ -97,8 +98,8 @@ public class DimensionRepositoryImpl implements DimensionRepository { if (dimensionFilter.getCreatedBy() != null) { dimensionDOExample.getOredCriteria().get(0).andCreatedByEqualTo(dimensionFilter.getCreatedBy()); } - if (dimensionFilter.getDomainId() != null) { - dimensionDOExample.getOredCriteria().get(0).andDomainIdEqualTo(dimensionFilter.getDomainId()); + if (CollectionUtils.isNotEmpty(dimensionFilter.getDomainIds())) { + dimensionDOExample.getOredCriteria().get(0).andDomainIdIn(dimensionFilter.getDomainIds()); } if (dimensionFilter.getSensitiveLevel() != null) { dimensionDOExample.getOredCriteria().get(0).andSensitiveLevelEqualTo(dimensionFilter.getSensitiveLevel()); diff --git a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/MetricRepositoryImpl.java b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/MetricRepositoryImpl.java index 0f0b80733..10acb3c5a 100644 --- a/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/MetricRepositoryImpl.java +++ b/semantic/model/src/main/java/com/tencent/supersonic/semantic/model/infrastructure/repository/MetricRepositoryImpl.java @@ -74,33 +74,7 @@ public class MetricRepositoryImpl implements MetricRepository { @Override public List getMetric(MetricFilter metricFilter) { - MetricDOExample metricDOExample = new MetricDOExample(); - metricDOExample.createCriteria(); - if (metricFilter.getId() != null) { - metricDOExample.getOredCriteria().get(0).andIdEqualTo(metricFilter.getId()); - } - if (metricFilter.getName() != null) { - metricDOExample.getOredCriteria().get(0).andNameLike("%" + metricFilter.getName() + "%"); - } - if (metricFilter.getBizName() != null) { - metricDOExample.getOredCriteria().get(0).andBizNameLike("%" + metricFilter.getBizName() + "%"); - } - if (metricFilter.getCreatedBy() != null) { - metricDOExample.getOredCriteria().get(0).andCreatedByEqualTo(metricFilter.getCreatedBy()); - } - if (metricFilter.getDomainId() != null) { - metricDOExample.getOredCriteria().get(0).andDomainIdEqualTo(metricFilter.getDomainId()); - } - if (metricFilter.getSensitiveLevel() != null) { - metricDOExample.getOredCriteria().get(0).andSensitiveLevelEqualTo(metricFilter.getSensitiveLevel()); - } - if (metricFilter.getStatus() != null) { - metricDOExample.getOredCriteria().get(0).andStatusEqualTo(metricFilter.getStatus()); - } - if (StringUtils.isNotBlank(metricFilter.getType())) { - metricDOExample.getOredCriteria().get(0).andTypeEqualTo(metricFilter.getType()); - } - return metricDOMapper.selectByExampleWithBLOBs(metricDOExample); + return metricDOCustomMapper.query(metricFilter); } @Override diff --git a/semantic/model/src/main/resources/mapper/custom/MetricDOCustomMapper.xml b/semantic/model/src/main/resources/mapper/custom/MetricDOCustomMapper.xml index 8054bdcc3..0e2047e18 100644 --- a/semantic/model/src/main/resources/mapper/custom/MetricDOCustomMapper.xml +++ b/semantic/model/src/main/resources/mapper/custom/MetricDOCustomMapper.xml @@ -97,4 +97,29 @@ + + diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/QueryParser.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/QueryParser.java index c2155807f..2d4455aff 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/QueryParser.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/QueryParser.java @@ -42,6 +42,7 @@ public class QueryParser { if (!parseSqlReq.getSql().isEmpty()) { return parser(parseSqlReq); } + metricReq.setNativeQuery(queryStructReq.getNativeQuery()); return parser(metricReq); } @@ -94,7 +95,7 @@ public class QueryParser { } public QueryStatement parser(MetricReq metricCommand) { - return parser(metricCommand, true); + return parser(metricCommand, !metricCommand.isNativeQuery()); } public QueryStatement parser(MetricReq metricCommand, boolean isAgg) { diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/calcite/sql/render/SourceRender.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/calcite/sql/render/SourceRender.java index ae6d087d0..db5c399dd 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/calcite/sql/render/SourceRender.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/calcite/sql/render/SourceRender.java @@ -2,12 +2,6 @@ package com.tencent.supersonic.semantic.query.parser.calcite.sql.render; import com.tencent.supersonic.semantic.api.query.request.MetricReq; -import com.tencent.supersonic.semantic.query.parser.calcite.sql.Renderer; -import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.DataSourceNode; -import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.DimensionNode; -import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.FilterNode; -import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.MetricNode; -import com.tencent.supersonic.semantic.query.parser.calcite.sql.TableView; import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Constants; import com.tencent.supersonic.semantic.query.parser.calcite.dsl.DataSource; import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Dimension; @@ -15,7 +9,13 @@ import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Identify; import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Measure; import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Metric; import com.tencent.supersonic.semantic.query.parser.calcite.schema.SemanticSchema; +import com.tencent.supersonic.semantic.query.parser.calcite.sql.Renderer; +import com.tencent.supersonic.semantic.query.parser.calcite.sql.TableView; +import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.DataSourceNode; +import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.DimensionNode; +import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.FilterNode; import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.IdentifyNode; +import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.MetricNode; import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.SemanticNode; import java.util.ArrayList; import java.util.HashSet; @@ -33,7 +33,7 @@ import org.springframework.util.CollectionUtils; @Slf4j public class SourceRender extends Renderer { - public static TableView renderOne(String alias, List fieldWhere, + public static TableView renderOne(String alias, List fieldWheres, List reqMetrics, List reqDimensions, String queryWhere, DataSource datasource, SqlValidatorScope scope, SemanticSchema schema, boolean nonAgg) throws Exception { @@ -41,11 +41,8 @@ public class SourceRender extends Renderer { TableView output = new TableView(); List queryMetrics = new ArrayList<>(reqMetrics); List queryDimensions = new ArrayList<>(reqDimensions); + List fieldWhere = new ArrayList<>(fieldWheres); if (!fieldWhere.isEmpty()) { -// SqlNode sqlNode = SemanticNode.parse(queryWhere, scope); -// if (addWhere) { -// output.getFilter().add(sqlNode); -// } Set dimensions = new HashSet<>(); Set metrics = new HashSet<>(); whereDimMetric(fieldWhere, queryMetrics, queryDimensions, datasource, schema, dimensions, metrics); @@ -69,7 +66,8 @@ public class SourceRender extends Renderer { } } for (String dimension : queryDimensions) { - if(dimension.contains(Constants.DIMENSION_IDENTIFY) && queryDimensions.contains(dimension.split(Constants.DIMENSION_IDENTIFY)[1])){ + if (dimension.contains(Constants.DIMENSION_IDENTIFY) && queryDimensions.contains( + dimension.split(Constants.DIMENSION_IDENTIFY)[1])) { continue; } buildDimension(dimension.contains(Constants.DIMENSION_IDENTIFY) ? dimension : "", @@ -213,14 +211,15 @@ public class SourceRender extends Renderer { continue; } String filterField = field; - if(field.contains(Constants.DIMENSION_IDENTIFY)) { + if (field.contains(Constants.DIMENSION_IDENTIFY)) { filterField = field.split(Constants.DIMENSION_IDENTIFY)[1]; } - addField(filterField,field,datasource,schema,dimensions,metrics); + addField(filterField, field, datasource, schema, dimensions, metrics); } } - private static void addField(String field,String oriField,DataSource datasource, SemanticSchema schema, Set dimensions, + private static void addField(String field, String oriField, DataSource datasource, SemanticSchema schema, + Set dimensions, Set metrics) { Optional dimension = datasource.getDimensions().stream() .filter(d -> d.getName().equalsIgnoreCase(field)).findFirst(); @@ -251,9 +250,11 @@ public class SourceRender extends Renderer { Optional datasourceMetric = schema.getMetrics() .stream().filter(m -> m.getName().equalsIgnoreCase(field)).findFirst(); if (datasourceMetric.isPresent()) { - Set measures = datasourceMetric.get().getMetricTypeParams().getMeasures().stream().map(m->m.getName()).collect( - Collectors.toSet()); - if(datasource.getMeasures().stream().map(m->m.getName()).collect(Collectors.toSet()).containsAll(measures)){ + Set measures = datasourceMetric.get().getMetricTypeParams().getMeasures().stream() + .map(m -> m.getName()).collect( + Collectors.toSet()); + if (datasource.getMeasures().stream().map(m -> m.getName()).collect(Collectors.toSet()) + .containsAll(measures)) { metrics.add(oriField); return; } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/convert/CalculateAggConverter.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/convert/CalculateAggConverter.java index 758ff94b7..58fc6cd16 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/convert/CalculateAggConverter.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/parser/convert/CalculateAggConverter.java @@ -197,9 +197,9 @@ public class CalculateAggConverter implements SemanticConverter { public String getOverSelect(QueryStructReq queryStructCmd, boolean isOver) { String aggStr = queryStructCmd.getAggregators().stream().map(f -> { if (f.getFunc().equals(AggOperatorEnum.RATIO_OVER) || f.getFunc().equals(AggOperatorEnum.RATIO_ROLL)) { - return String.format("( (%s-%s_roll)/cast(%s_roll as DOUBLE) ) as %s", + return String.format("( (%s-%s_roll)/cast(%s_roll as DOUBLE) ) as %s_%s,%s", f.getColumn(), f.getColumn(), f.getColumn(), f.getColumn(), - f.getColumn()); + f.getFunc().getOperator(),f.getColumn()); } else { return f.getColumn(); } @@ -335,9 +335,9 @@ public class CalculateAggConverter implements SemanticConverter { String aggStr = queryStructCmd.getAggregators().stream().map(f -> { if (f.getFunc().equals(AggOperatorEnum.RATIO_OVER) || f.getFunc().equals(AggOperatorEnum.RATIO_ROLL)) { return String.format( - "if(%s_roll!=0, (%s-%s_roll)/%s_roll , 0) as %s", + "if(%s_roll!=0, (%s-%s_roll)/%s_roll , 0) as %s_%s,%s", f.getColumn(), f.getColumn(), f.getColumn(), f.getColumn(), - f.getColumn()); + f.getColumn(),f.getFunc().getOperator(),f.getColumn()); } else { return f.getColumn(); } diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/DateUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/DateUtils.java index 978143f3e..01573fddd 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/DateUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/DateUtils.java @@ -39,7 +39,7 @@ public class DateUtils { private String sysDateWeekCol; public Boolean recentMode(DateConf dateInfo) { - if (Objects.nonNull(dateInfo) && DateConf.DateMode.RECENT_UNITS == dateInfo.getDateMode() + if (Objects.nonNull(dateInfo) && DateConf.DateMode.RECENT == dateInfo.getDateMode() && DAY.equalsIgnoreCase(dateInfo.getPeriod()) && Objects.nonNull(dateInfo.getUnit())) { return true; } @@ -47,7 +47,7 @@ public class DateUtils { } public boolean hasAvailableDataMode(DateConf dateInfo) { - if (Objects.nonNull(dateInfo) && DateConf.DateMode.AVAILABLE_TIME == dateInfo.getDateMode()) { + if (Objects.nonNull(dateInfo) && DateConf.DateMode.AVAILABLE == dateInfo.getDateMode()) { return true; } return false; @@ -263,16 +263,16 @@ public class DateUtils { public String getDateWhereStr(DateConf dateInfo, ItemDateResp dateDate) { String dateStr = ""; switch (dateInfo.getDateMode()) { - case BETWEEN_CONTINUOUS: + case BETWEEN: dateStr = betweenDateStr(dateDate, dateInfo); break; - case LIST_DISCRETE: + case LIST: dateStr = listDateStr(dateDate, dateInfo); break; - case RECENT_UNITS: + case RECENT: dateStr = recentDateStr(dateDate, dateInfo); break; - case AVAILABLE_TIME: + case AVAILABLE: dateStr = hasDataModeStr(dateDate, dateInfo); break; default: diff --git a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/QueryStructUtils.java b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/QueryStructUtils.java index 2b7f87ac8..23d5756a2 100644 --- a/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/QueryStructUtils.java +++ b/semantic/query/src/main/java/com/tencent/supersonic/semantic/query/utils/QueryStructUtils.java @@ -101,10 +101,10 @@ public class QueryStructUtils { if (Objects.isNull(dateDate) || Strings.isEmpty(dateDate.getStartDate()) && Strings.isEmpty(dateDate.getEndDate())) { - if (dateInfo.getDateMode().equals(DateMode.LIST_DISCRETE)) { + if (dateInfo.getDateMode().equals(DateMode.LIST)) { return dateUtils.listDateStr(dateDate, dateInfo); } - if (dateInfo.getDateMode().equals(DateMode.BETWEEN_CONTINUOUS)) { + if (dateInfo.getDateMode().equals(DateMode.BETWEEN)) { return dateUtils.betweenDateStr(dateDate, dateInfo); } if (dateUtils.hasAvailableDataMode(dateInfo)) {