Initial commit. master
authorZoltán Felleg <zoltan.felleg@qqcs.org>
Tue, 16 May 2023 15:25:35 +0000 (17:25 +0200)
committerZoltán Felleg <zoltan.felleg@qqcs.org>
Tue, 16 May 2023 15:25:35 +0000 (17:25 +0200)
257 files changed:
.gitignore [new file with mode: 0644]
CommonDto/pom.xml [new file with mode: 0644]
Core/Dockerfile [new file with mode: 0644]
Core/pom.xml [new file with mode: 0644]
Core/src/main/java/hu/user/core/CoreApplication.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/config/RestConfig.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/config/StatisticConfig.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/config/WebServiceConfig.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/controller/EstimatorController.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/controller/StatisticController.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/domain/Fraud.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/dto/CommulatedResponseFromEstimator.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/dto/ObservedValue.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/dto/ResponseFromSingleEstimator.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/dto/StatisticsDto.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/endpoint/WebserviceEndpoint.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/estimators/EstimatorContainer.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/estimators/WeightAndTransactionIdentifierFieldName.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/repository/FraudRepository.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/service/FraudService.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/service/PredictionService.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/service/StatisticService.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/service/TrainClient.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/service/UrlBuilder.java [new file with mode: 0644]
Core/src/main/java/hu/user/core/service/VoteModul.java [new file with mode: 0644]
Core/src/main/resources/application.properties [new file with mode: 0644]
Core/src/main/resources/application.yml [new file with mode: 0644]
Core/src/main/resources/detection.xsd [new file with mode: 0644]
Core/src/test/java/hu/user/core/CoreApplicationTests.java [new file with mode: 0644]
CoreCli/.mvn/wrapper/maven-wrapper.jar [new file with mode: 0644]
CoreCli/.mvn/wrapper/maven-wrapper.properties [new file with mode: 0644]
CoreCli/mvnw [new file with mode: 0755]
CoreCli/mvnw.cmd [new file with mode: 0644]
CoreCli/pom.xml [new file with mode: 0644]
CoreCli/src/main/java/hu/user/corecli/CoreCliApplication.java [new file with mode: 0644]
CoreCli/src/main/java/hu/user/corecli/config/RestConfig.java [new file with mode: 0644]
CoreCli/src/main/java/hu/user/corecli/dto/ConfusionMatrix.java [new file with mode: 0644]
CoreCli/src/main/java/hu/user/corecli/service/CoreClient.java [new file with mode: 0644]
CoreCli/src/main/java/hu/user/corecli/service/EstimatorClient.java [new file with mode: 0644]
CoreCli/src/main/resources/application-mkigui.properties [new file with mode: 0644]
CoreCli/src/main/resources/application.properties [new file with mode: 0644]
CoreCli/src/main/resources/wsdl/detection.wsdl [new file with mode: 0644]
CoreCli/src/test/java/hu/user/corecli/CoreCliApplicationTests.java [new file with mode: 0644]
CoreManager/Dockerfile [new file with mode: 0644]
CoreManager/pom.xml [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/CoreManagerApplication.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/config/CoreSoapClientConfiguration.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/config/DevelopmentConfig.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/config/RestConfig.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/ConfusionMatrixDto.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/DirectoryProperty.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/DownStreamErrorDto.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/EstimatorMetricsDto.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/EstimatorParameter.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/FieldProperty.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/FileProperty.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/GenericRecordCollectionDto.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/MetricsAndTrainTaskName.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/MetricsDto.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/dto/ObservedValue.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/exception/ParametersExistYetException.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/service/CoreClient.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/service/CoreSoapClient.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/service/EstimatorClient.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/service/EvaluationService.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/service/FileHandler.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/service/TrainClient.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/service/UrlBuilder.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/viewmodel/EstimatorRegistryViewModel.java [new file with mode: 0644]
CoreManager/src/main/java/hu/user/coremanager/viewmodel/QualifyingViewModel.java [new file with mode: 0644]
CoreManager/src/main/resources/META-INF/spring-devtools.properties [new file with mode: 0644]
CoreManager/src/main/resources/application.properties [new file with mode: 0644]
CoreManager/src/main/resources/application.yml [new file with mode: 0644]
CoreManager/src/main/resources/maven/generate [new file with mode: 0644]
CoreManager/src/main/resources/metainfo.zk/lang-addon.xml [new file with mode: 0644]
CoreManager/src/main/resources/metainfo.zk/zk.xml [new file with mode: 0644]
CoreManager/src/main/resources/static/css/static-globalstyles.css [new file with mode: 0644]
CoreManager/src/main/resources/static/css/static-localstyles.css [new file with mode: 0644]
CoreManager/src/main/resources/static/img/zklogo1.png [new file with mode: 0644]
CoreManager/src/main/resources/web/css/airplane_background.jpg [new file with mode: 0644]
CoreManager/src/main/resources/web/css/mySpecial.css [new file with mode: 0644]
CoreManager/src/main/resources/web/css/plane.jpg [new file with mode: 0644]
CoreManager/src/main/resources/web/css/style.css [new file with mode: 0644]
CoreManager/src/main/resources/web/img/airplane_background.jpg [new file with mode: 0644]
CoreManager/src/main/resources/web/img/logo.png [new file with mode: 0644]
CoreManager/src/main/resources/web/img/zklogo3.png [new file with mode: 0644]
CoreManager/src/main/resources/web/zul/estimatorRegistry.zul [new file with mode: 0644]
CoreManager/src/main/resources/web/zul/qualifying.zul [new file with mode: 0644]
CoreManager/src/main/resources/web/zul/start.zul [new file with mode: 0644]
CoreManager/src/main/resources/wsdl/detection.wsdl [new file with mode: 0644]
CoreManager/src/test/java/hu/user/coremanager/CoreManagerApplicationTests.java [new file with mode: 0644]
Estimator/Dockerfile [new file with mode: 0644]
Estimator/requirements.txt [new file with mode: 0644]
Estimator/src/__pycache__/database_module.cpython-37.pyc [new file with mode: 0644]
Estimator/src/__pycache__/engineering_module.cpython-37.pyc [new file with mode: 0644]
Estimator/src/database_module.py [new file with mode: 0644]
Estimator/src/engineering_module.py [new file with mode: 0644]
Estimator/src/server.py [new file with mode: 0644]
Train/Dockerfile [new file with mode: 0644]
Train/requirements.txt [new file with mode: 0644]
Train/src/SQL CREATE SCHEMA common_fraud.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE encoded_table_registry.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE encoding.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE estimator.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE feature_engineered_table_registry.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE feature_engineering.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE fraud.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE label_encoder.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE metrics.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE planned_encoding_and_feature_engineering.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE raw_dataset.txt [new file with mode: 0644]
Train/src/SQL CREATE TABLE train_task.txt [new file with mode: 0644]
Train/src/Start RabbitMQ [new file with mode: 0644]
Train/src/__pycache__/database_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/ddl_build_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/encoding_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/engineering_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/error_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/exception_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/server.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/test_ddl_build_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/test_encoding_module.cpython-37.pyc [new file with mode: 0644]
Train/src/__pycache__/test_server_module.cpython-37.pyc [new file with mode: 0644]
Train/src/database_module.py [new file with mode: 0644]
Train/src/ddl_build_module.py [new file with mode: 0644]
Train/src/encoding_module.py [new file with mode: 0644]
Train/src/engineering_module.py [new file with mode: 0644]
Train/src/error_module.py [new file with mode: 0644]
Train/src/exception_module.py [new file with mode: 0644]
Train/src/requirements.txt [new file with mode: 0644]
Train/src/server.py [new file with mode: 0644]
Train/src/test_ddl_build_module.py [new file with mode: 0644]
Train/src/test_encoding_module.py [new file with mode: 0644]
Train/src/test_server_module.py [new file with mode: 0644]
Train/test_ddl_build_module.py [new file with mode: 0644]
TrainCli/.mvn/wrapper/maven-wrapper.jar [new file with mode: 0644]
TrainCli/.mvn/wrapper/maven-wrapper.properties [new file with mode: 0644]
TrainCli/mvnw [new file with mode: 0755]
TrainCli/mvnw.cmd [new file with mode: 0644]
TrainCli/pom.xml [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/ParametersExistYetException.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/TrainCliApplication.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/config/RabbitMqConfig.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/config/RestConfig.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/config/SchedulerConfig.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/config/SchedulerJobFactory.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/CommonResponse.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/CommonTrainParameters.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/EncodingAndFeatureEngineeringParameters.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/EstimatorIdentifiers.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/FeatureEnginneringParameters.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/FieldProperty.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/FitParameters.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/FraudCandidate.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/LivenessDto.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/TableProperties.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/dto/TrainTaskParameters.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/error/RestTemplateErrorHandler.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/event/TrainLiveCheckEvent.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/event/TrainTaskEvent.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/listenner/TrainQueueListener.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/scheduled/JobScheduleCreator.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/scheduled/job/ShowProgressJob.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/scheduled/job/TrainLivenessChecker.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/scheduled/jobinfo/SchedulerJobInfo.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/scheduled/liveness/LivenessCheck.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/scheduled/progress/ShowProgress.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/service/JsonConverter.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/service/ProgressPresenter.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/service/SchedulerJobService.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/service/TrainClient.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/service/UrlBuilder.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/status/TrainStatus.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/train/ProgressName.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/train/ProgressState.java [new file with mode: 0644]
TrainCli/src/main/java/hu/user/traincli/train/ProgressStatus.java [new file with mode: 0644]
TrainCli/src/main/resources/application.properties [new file with mode: 0644]
TrainCli/src/main/resources/task.json [new file with mode: 0644]
TrainCli/src/test/java/hu/user/traincli/TrainCliApplicationTests.java [new file with mode: 0644]
TrainManager/Dockerfile [new file with mode: 0644]
TrainManager/pom.xml [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/TrainManagerApplication.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/component/TrainProgressStateHolder.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/config/DevelopmentConfig.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/config/RabbitMqConfig.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/config/RestConfig.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/controller/TrainTaskController.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/CommonResponse.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/CommonTrainParameters.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/ConfusionMatrixDto.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/DirectoryProperty.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/DownStreamErrorDto.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/EncodingAndFeatureEngineeringParameters.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/EstimatorAndTrainTaskIdentifiers.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/FeatureEnginneringParameters.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/FieldProperty.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/FileProperty.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/FitParameters.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/FraudCandidate.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/LivenessDto.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/TableProperties.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/TrainTaskParameters.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/csv/CsvContentDto.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/csv/CsvDataHolder.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/csv/ReducingAndEvaulationParameterRequestDto.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/csv/ReducingAndEvaulationParameterResponseDto.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/display/EstimatorMetricsDto.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/display/FieldNameAndEncodingType.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/progress/TrainProgressScreenProperties.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/dto/progress/TrainProgressState.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/event/MessageArrivedFromTrainModule.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/event/TrainLiveCheckEvent.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskEvent.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskMethodName.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskMethodState.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/exception/ParametersExistYetException.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/listenner/TrainQueueListener.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/service/CsvHandler.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/service/MetricsCsvDataBuilder.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/service/TrainClient.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/service/UrlBuilder.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/status/TrainModuleStatus.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/status/TrainStatus.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepName.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepState.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepStatus.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/status/TrainTaskState.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/viewmodel/DatabaseViewModel.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/viewmodel/InputTrainParameterViewModel.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/viewmodel/MetricsViewModel.java [new file with mode: 0644]
TrainManager/src/main/java/hu/user/trainmanager/viewmodel/ProgressPresenter.java [new file with mode: 0644]
TrainManager/src/main/resources/META-INF/spring-devtools.properties [new file with mode: 0644]
TrainManager/src/main/resources/application.properties [new file with mode: 0644]
TrainManager/src/main/resources/application.yml [new file with mode: 0644]
TrainManager/src/main/resources/metainfo.zk/lang-addon.xml [new file with mode: 0644]
TrainManager/src/main/resources/metainfo.zk/zk-dev.xml [new file with mode: 0644]
TrainManager/src/main/resources/metainfo.zk/zk.xml [new file with mode: 0644]
TrainManager/src/main/resources/web/css/mySpecial.css [new file with mode: 0644]
TrainManager/src/main/resources/web/css/zk-admin-bootstrap-theme.css [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/database.zul [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/empty.zul [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/fit.zul [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/inputTrainParameter.zul [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/metrics.zul [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/metricsSave.zul [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/presentEncodingAndFeatureEngineering.zul [new file with mode: 0644]
TrainManager/src/main/resources/web/zul/start.zul [new file with mode: 0644]
TrainManager/src/test/java/hu/user/trainmanager/TrainManagerApplicationTests.java [new file with mode: 0644]
docker-compose.yml [new file with mode: 0644]
docker-compose_2023_03_18.yml [new file with mode: 0644]
docker-compose_2023_03_19.yml [new file with mode: 0644]
docker-compose_2023_03_20.yml [new file with mode: 0644]
docker-compose_2023_04_18.yml [new file with mode: 0644]
docker_compose_2022_02_24.yml [new file with mode: 0644]
mysql-docker-compose.yml [new file with mode: 0644]
pom.xml [new file with mode: 0644]
thering@mkigui [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..549e00a
--- /dev/null
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/CommonDto/pom.xml b/CommonDto/pom.xml
new file mode 100644 (file)
index 0000000..e976f76
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>hu.user</groupId>
+        <artifactId>CreditCardFraudDetect</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>CommonDto</artifactId>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>2.14.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jdk8</artifactId>
+            <version>2.14.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.24</version>
+            <optional>true</optional>
+        </dependency>
+
+
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/Core/Dockerfile b/Core/Dockerfile
new file mode 100644 (file)
index 0000000..c3c2d68
--- /dev/null
@@ -0,0 +1,4 @@
+FROM adoptopenjdk:14-jre-hotspot
+WORKDIR /opt/app
+COPY target/*.jar core.jar
+CMD ["java","-jar","core.jar"]
\ No newline at end of file
diff --git a/Core/pom.xml b/Core/pom.xml
new file mode 100644 (file)
index 0000000..5ec5a64
--- /dev/null
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.6.1</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>hu.user</groupId>
+    <artifactId>Core</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>Core</name>
+    <description>Core</description>
+    <properties>
+        <java.version>11</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web-services</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>wsdl4j</groupId>
+            <artifactId>wsdl4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jvnet.jaxb2_commons</groupId>
+            <artifactId>jaxb2-basics</artifactId>
+            <version>0.9.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jvnet.jaxb2_commons</groupId>
+            <artifactId>jaxb2-value-constructor</artifactId>
+            <version>3.0</version>
+        </dependency>
+            <dependency>
+                <groupId>org.glassfish.jaxb</groupId>
+                <artifactId>jaxb-runtime</artifactId>
+            </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>hu.user</groupId>
+            <artifactId>CommonDto</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jaxb2-maven-plugin</artifactId>
+                <version>2.5.0</version>
+                <executions>
+                    <execution>
+                        <id>xjc</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                        <configuration>
+                            <arguments>
+                                <argument>-Xvalue-constructor</argument>
+                            </arguments>
+                        </configuration>
+                    </execution>
+                </executions>
+                <configuration>
+                    <sources>
+                        <source>${project.basedir}/src/main/resources/detection.xsd</source>
+                    </sources>
+                </configuration>
+            </plugin>
+
+        </plugins>
+    </build>
+
+</project>
diff --git a/Core/src/main/java/hu/user/core/CoreApplication.java b/Core/src/main/java/hu/user/core/CoreApplication.java
new file mode 100644 (file)
index 0000000..14159e3
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.core;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CoreApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(CoreApplication.class, args);
+    }
+
+}
diff --git a/Core/src/main/java/hu/user/core/config/RestConfig.java b/Core/src/main/java/hu/user/core/config/RestConfig.java
new file mode 100644 (file)
index 0000000..8749e49
--- /dev/null
@@ -0,0 +1,18 @@
+package hu.user.core.config;
+
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestConfig {
+
+    @Bean
+    public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
+        RestTemplate restTemplate = builder
+                .build();
+        return restTemplate;
+
+    }
+}
diff --git a/Core/src/main/java/hu/user/core/config/StatisticConfig.java b/Core/src/main/java/hu/user/core/config/StatisticConfig.java
new file mode 100644 (file)
index 0000000..b930367
--- /dev/null
@@ -0,0 +1,31 @@
+package hu.user.core.config;
+
+import org.springframework.stereotype.Component;
+
+import hu.user.core.dto.StatisticsDto;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Component
+@Getter
+@Setter
+public class StatisticConfig {
+
+       private String cardNumberFieldName;
+       private String transactionTimeFieldName;
+       private boolean enabled;
+
+       public void setParameters(StatisticsDto statisticsDto) {
+               this.cardNumberFieldName = statisticsDto.getCardNumberFieldName();
+               this.transactionTimeFieldName = statisticsDto.getTransactionTimeFieldName();
+               this.enabled = enabled;
+       }
+
+       public void deleteParameters() {
+               this.cardNumberFieldName = null;
+               this.transactionTimeFieldName = null;
+               this.enabled = false;
+       }
+
+}
diff --git a/Core/src/main/java/hu/user/core/config/WebServiceConfig.java b/Core/src/main/java/hu/user/core/config/WebServiceConfig.java
new file mode 100644 (file)
index 0000000..8036346
--- /dev/null
@@ -0,0 +1,40 @@
+package hu.user.core.config;
+
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.ws.config.annotation.EnableWs;
+import org.springframework.ws.config.annotation.WsConfigurerAdapter;
+import org.springframework.ws.transport.http.MessageDispatcherServlet;
+import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
+import org.springframework.xml.xsd.SimpleXsdSchema;
+import org.springframework.xml.xsd.XsdSchema;
+
+@EnableWs
+@Configuration
+public class WebServiceConfig extends WsConfigurerAdapter {
+       @Bean
+       public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
+               MessageDispatcherServlet servlet = new MessageDispatcherServlet();
+               servlet.setApplicationContext(applicationContext);
+               servlet.setTransformWsdlLocations(true);
+               return new ServletRegistrationBean<>(servlet, "/ws/*");
+       }
+
+       @Bean(name = "detection")
+       public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
+               DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
+               wsdl11Definition.setPortTypeName("DetectionPort");
+               wsdl11Definition.setLocationUri("/ws");
+               wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service");
+               wsdl11Definition.setSchema(countriesSchema);
+               return wsdl11Definition;
+       }
+
+       @Bean
+       public XsdSchema countriesSchema() {
+               return new SimpleXsdSchema(new ClassPathResource("detection.xsd"));
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/controller/EstimatorController.java b/Core/src/main/java/hu/user/core/controller/EstimatorController.java
new file mode 100644 (file)
index 0000000..b054c83
--- /dev/null
@@ -0,0 +1,50 @@
+package hu.user.core.controller;
+
+import java.util.List;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import hu.user.common.dto.EstimatorRegistryParameterDto;
+import hu.user.core.estimators.EstimatorContainer;
+
+@Controller
+public class EstimatorController {
+
+       private EstimatorContainer estimatorContainer;
+
+       public EstimatorController(EstimatorContainer estimatorContainer) {
+               this.estimatorContainer = estimatorContainer;
+       }
+
+       @PostMapping("/estimator")
+       public ResponseEntity<List<EstimatorRegistryParameterDto>> addEstimator(
+                       @RequestBody EstimatorRegistryParameterDto estimatorRegistryParameterDto) {
+               List<EstimatorRegistryParameterDto> registeredEstimators =
+                               estimatorContainer.addEstimator(estimatorRegistryParameterDto);
+               return new ResponseEntity<>(registeredEstimators, HttpStatus.OK);
+       }
+
+       @GetMapping("/estimator")
+       public ResponseEntity<List<EstimatorRegistryParameterDto>> getEstimators() {
+               return new ResponseEntity<>(estimatorContainer.getEstimator(), HttpStatus.OK);
+       }
+
+       @PutMapping("estimator")
+       public ResponseEntity<List<EstimatorRegistryParameterDto>> modifyEstimator(
+                       @RequestBody EstimatorRegistryParameterDto estimatorRegistryParameterDto) {
+               return new ResponseEntity<>(estimatorContainer.updateEstimator(estimatorRegistryParameterDto), HttpStatus.OK);
+       }
+
+       @DeleteMapping("estimator/{id}")
+       public ResponseEntity<List<EstimatorRegistryParameterDto>> deleteEstimator(@PathVariable("id") int id) {
+               return new ResponseEntity<>(estimatorContainer.deleteEstimator(id), HttpStatus.OK);
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/controller/StatisticController.java b/Core/src/main/java/hu/user/core/controller/StatisticController.java
new file mode 100644 (file)
index 0000000..e1cf4dd
--- /dev/null
@@ -0,0 +1,57 @@
+package hu.user.core.controller;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import hu.user.common.dto.ConfusionMatrixDto;
+import hu.user.core.dto.ObservedValue;
+import hu.user.core.service.FraudService;
+import hu.user.core.service.StatisticService;
+
+@Controller
+public class StatisticController {
+
+       private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+       @Autowired private FraudService fraudService;
+       @Autowired private StatisticService statisticService;
+
+       @GetMapping("/confusion_matrix")
+       public ResponseEntity<ConfusionMatrixDto> getConfusionMatrix() {
+               logger.info("get confusion matrix");
+               return new ResponseEntity<>(fraudService.qualify(), HttpStatus.OK);
+       }
+
+       @PostMapping("/observed")
+       public ResponseEntity<?> storeObservedValues(@RequestBody List<ObservedValue> observedValues) {
+               statisticService.storeObservedValues(observedValues);
+               return new ResponseEntity<>(HttpStatus.OK);
+       }
+
+       @GetMapping("/transaction")
+       public ResponseEntity<Integer> getAllTransaction() {
+               return new ResponseEntity<>(statisticService.getAllTransaction(), HttpStatus.OK);
+       }
+
+       @GetMapping("/observed")
+       public ResponseEntity<Integer> getAllObservedTransaction() {
+               return new ResponseEntity<>(statisticService.getAllObservedTransaction(), HttpStatus.OK);
+       }
+
+       @PostMapping("/transaction_clear")
+       public ResponseEntity<?> deleteAllTransaction() {
+               fraudService.deleteAllFraud();
+               return new ResponseEntity<>(HttpStatus.OK);
+       }
+       }
+
+
diff --git a/Core/src/main/java/hu/user/core/domain/Fraud.java b/Core/src/main/java/hu/user/core/domain/Fraud.java
new file mode 100644 (file)
index 0000000..bd03811
--- /dev/null
@@ -0,0 +1,44 @@
+package hu.user.core.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Entity
+@Getter
+@Setter
+public class Fraud {
+
+    @Id
+//    @GeneratedValue(strategy = GenerationType.AUTO)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long Id;
+    private String transactionUuid;
+    private Integer predictedValue;
+
+    private Integer observedValue;
+
+    private boolean predicted;
+
+    private boolean observed;
+
+    private double positiveProbability;
+
+    private double negativeProbability;
+
+    public Fraud(String transactionUuid, int predictedValue,double positiveProbability,double negativeProbability) {
+        this.transactionUuid = transactionUuid;
+        this.predictedValue = predictedValue;
+        this.predicted = true;
+        this.observed = false;
+        this.positiveProbability = positiveProbability;
+        this.negativeProbability = negativeProbability;
+    }
+
+    public Fraud() {
+    }
+}
diff --git a/Core/src/main/java/hu/user/core/dto/CommulatedResponseFromEstimator.java b/Core/src/main/java/hu/user/core/dto/CommulatedResponseFromEstimator.java
new file mode 100644 (file)
index 0000000..f8619d5
--- /dev/null
@@ -0,0 +1,23 @@
+package hu.user.core.dto;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class CommulatedResponseFromEstimator implements Serializable {
+
+    private Map<Integer, ResponseFromSingleEstimator> response = new HashMap<>();
+
+}
diff --git a/Core/src/main/java/hu/user/core/dto/ObservedValue.java b/Core/src/main/java/hu/user/core/dto/ObservedValue.java
new file mode 100644 (file)
index 0000000..f71ae92
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.core.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class ObservedValue {
+
+       private String uuid;
+       private int observedFraud;
+}
diff --git a/Core/src/main/java/hu/user/core/dto/ResponseFromSingleEstimator.java b/Core/src/main/java/hu/user/core/dto/ResponseFromSingleEstimator.java
new file mode 100644 (file)
index 0000000..9bf0cdd
--- /dev/null
@@ -0,0 +1,29 @@
+package hu.user.core.dto;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+@Setter
+public class ResponseFromSingleEstimator implements Serializable {
+
+       protected int prediction;
+       protected double negativeProbability;
+       protected double positiveProbability;
+
+       public ResponseFromSingleEstimator(@JsonProperty(value = "prediction", required = true) int prediction,
+                       @JsonProperty(value = "negative_probability", required = true) double negativeProbability,
+                       @JsonProperty(value = "positive_probability", required = true) double positiveProbability) {
+               this.prediction = prediction;
+               this.negativeProbability = negativeProbability;
+               this.positiveProbability = positiveProbability;
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/dto/StatisticsDto.java b/Core/src/main/java/hu/user/core/dto/StatisticsDto.java
new file mode 100644 (file)
index 0000000..9faa917
--- /dev/null
@@ -0,0 +1,22 @@
+package hu.user.core.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+
+@Getter
+public class StatisticsDto {
+
+       private String cardNumberFieldName;
+       private String transactionTimeFieldName;
+       private boolean enabled;
+
+       public StatisticsDto(@JsonProperty(value = "card_number_field_name", required = true) String cardNumberFieldName,
+                       @JsonProperty(value = "transaction_time_field_name", required = true) String transactionTimeFieldName,
+                       @JsonProperty(value = "enabled", required = true) boolean enabled) {
+
+               this.cardNumberFieldName = cardNumberFieldName;
+               this.transactionTimeFieldName = transactionTimeFieldName;
+               this.enabled = enabled;
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/endpoint/WebserviceEndpoint.java b/Core/src/main/java/hu/user/core/endpoint/WebserviceEndpoint.java
new file mode 100644 (file)
index 0000000..196e7e8
--- /dev/null
@@ -0,0 +1,65 @@
+package hu.user.core.endpoint;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.ws.server.endpoint.annotation.Endpoint;
+import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
+import org.springframework.ws.server.endpoint.annotation.RequestPayload;
+import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
+
+import hu.user.core.service.StatisticService;
+import hu.user.core.service.VoteModul;
+import io.spring.guides.gs_producing_web_service.DetectionResponse;
+import io.spring.guides.gs_producing_web_service.GenerateDetectionRequest;
+import io.spring.guides.gs_producing_web_service.GenerateGenericDetectionRequest;
+import io.spring.guides.gs_producing_web_service.ObjectFactory;
+import io.spring.guides.gs_producing_web_service.GenerateDetectionResponse;
+import io.spring.guides.gs_producing_web_service.StoreRealTransactionRequest;
+import io.spring.guides.gs_producing_web_service.StoreRealTransactionResponse;
+import io.spring.guides.gs_producing_web_service.StoreResult;
+import lombok.AllArgsConstructor;
+
+@Endpoint
+@AllArgsConstructor
+public class WebserviceEndpoint {
+
+       private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service";
+
+        private VoteModul voteModul;
+       private StatisticService statisticService;
+
+
+
+       @PayloadRoot(namespace = NAMESPACE_URI, localPart = "generateDetectionRequest")
+       @ResponsePayload
+       public GenerateDetectionResponse getPrediction(@RequestPayload GenerateDetectionRequest generateDetectionRequest) {
+               ObjectFactory objectFactory =new  ObjectFactory();
+               DetectionResponse detectionResponse = voteModul.voting(generateDetectionRequest);
+               GenerateDetectionResponse generateDetectionResponse = objectFactory.createGenerateDetectionResponse();
+               generateDetectionResponse.setDetectionResponse(detectionResponse);
+               return generateDetectionResponse;
+       }
+
+       @PayloadRoot(namespace = NAMESPACE_URI, localPart = "generateGenericDetectionRequest")
+       @ResponsePayload
+       public GenerateDetectionResponse getGenericPrediction(@RequestPayload
+       GenerateGenericDetectionRequest generateGenericDetectionRequest) {
+               ObjectFactory objectFactory =new  ObjectFactory();
+               DetectionResponse detectionResponse = voteModul.genericVoting(generateGenericDetectionRequest);
+               GenerateDetectionResponse generateDetectionResponse = objectFactory.createGenerateDetectionResponse();
+               generateDetectionResponse.setDetectionResponse(detectionResponse);
+               return generateDetectionResponse;
+       }
+
+       @PayloadRoot(namespace = NAMESPACE_URI, localPart = "storeRealTransactionRequest")
+       @ResponsePayload
+       public StoreRealTransactionResponse storeRealTransactions(
+                       @RequestPayload StoreRealTransactionRequest storeRealTransactionRequest) {
+               ObjectFactory objectFactory =new  ObjectFactory();
+               StoreRealTransactionResponse storeRealTransactionResponse=objectFactory.createStoreRealTransactionResponse();
+               StoreResult storeResult = statisticService.storeRealTransactions(storeRealTransactionRequest);
+               storeRealTransactionResponse.setStoreResult(storeResult);
+               return storeRealTransactionResponse;
+
+       }
+
+}
diff --git a/Core/src/main/java/hu/user/core/estimators/EstimatorContainer.java b/Core/src/main/java/hu/user/core/estimators/EstimatorContainer.java
new file mode 100644 (file)
index 0000000..4709ab2
--- /dev/null
@@ -0,0 +1,69 @@
+package hu.user.core.estimators;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.stereotype.Component;
+
+import hu.user.common.dto.EstimatorRegistryParameterDto;
+import hu.user.core.service.TrainClient;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+@Component
+@Getter
+@Setter
+@Slf4j
+public class EstimatorContainer {
+
+       private static final String ID = "id";
+
+       private TrainClient trainClient;
+       private Map<Integer, Integer> estimatorWeightById = new HashMap<>();
+       private String transactionIdentifierFieldName;
+
+       public EstimatorContainer(TrainClient trainClient) {
+               this.trainClient = trainClient;
+       }
+
+       public List<EstimatorRegistryParameterDto> addEstimator(EstimatorRegistryParameterDto registryParameterDto) {
+               if (transactionIdentifierFieldName == null) {
+                       transactionIdentifierFieldName = trainClient.getTransactionIdentifierFieldNameByEstimatorId(
+                                       Map.of(ID, String.valueOf(registryParameterDto.getEstimatorId())));
+               }
+               estimatorWeightById.put(registryParameterDto.getEstimatorId(), registryParameterDto.getWeight());
+               log.debug(
+                               "Estimator registered, estimator id: " + registryParameterDto.getEstimatorId() + ", weight: " + registryParameterDto.getWeight());
+               return convertToDtos();
+       }
+
+       public List<EstimatorRegistryParameterDto> getEstimator() {
+               return convertToDtos();
+       }
+
+       public List<EstimatorRegistryParameterDto> updateEstimator(EstimatorRegistryParameterDto registryParameterDto) {
+               int id = registryParameterDto.getEstimatorId();
+               if (estimatorWeightById.containsKey(id)) {
+                       estimatorWeightById.put(registryParameterDto.getEstimatorId(), registryParameterDto.getWeight());
+                       log.debug(
+                                       "Estimator weight updated, estimator id: " + registryParameterDto.getEstimatorId() + ", weight: " + registryParameterDto.getWeight());
+               }
+               return convertToDtos();
+       }
+
+       public List<EstimatorRegistryParameterDto> deleteEstimator(int estimatorId) {
+               if (estimatorWeightById.containsKey(estimatorId)) {
+                       estimatorWeightById.remove(estimatorId);
+                       log.debug("Estimator removed, estimator id: " + estimatorId);
+               }
+               return convertToDtos();
+       }
+
+       private List<EstimatorRegistryParameterDto> convertToDtos() {
+               return estimatorWeightById.entrySet().stream()
+                               .map(e -> new EstimatorRegistryParameterDto(e.getKey(), e.getValue())).collect(Collectors.toList());
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/estimators/WeightAndTransactionIdentifierFieldName.java b/Core/src/main/java/hu/user/core/estimators/WeightAndTransactionIdentifierFieldName.java
new file mode 100644 (file)
index 0000000..094b717
--- /dev/null
@@ -0,0 +1,15 @@
+package hu.user.core.estimators;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class WeightAndTransactionIdentifierFieldName {
+
+       private int weight;
+       private String transactionIdentifierFieldName;
+
+}
diff --git a/Core/src/main/java/hu/user/core/repository/FraudRepository.java b/Core/src/main/java/hu/user/core/repository/FraudRepository.java
new file mode 100644 (file)
index 0000000..ae08e2a
--- /dev/null
@@ -0,0 +1,23 @@
+package hu.user.core.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import hu.user.core.domain.Fraud;
+
+public interface FraudRepository extends JpaRepository<Fraud,Long> {
+
+
+       @Query("SELECT COUNT(f) FROM Fraud f WHERE f.predicted = true and f.observed=true and f.predictedValue=1 and f.observedValue=1")
+       int getNumberOfTruePositivePrediction();
+       @Query("SELECT COUNT(f) FROM Fraud f WHERE f.predicted = true and f.observed=true and f.predictedValue=1 and f.observedValue=0")
+       int getNumberOfFalsePositivePrediction();
+       @Query("SELECT COUNT(f) FROM Fraud f WHERE f.predicted = true and f.observed=true and f.predictedValue=0 and f.observedValue=0")
+       int getNumberOfTrueNegativePrediction();
+       @Query("SELECT COUNT(f) FROM Fraud f WHERE f.predicted = true and f.observed=true and f.predictedValue=0 and f.observedValue=1")
+       int getNumberOfFalseNegativePrediction();
+       public Fraud getFraudByTransactionUuid(String transactionUuid);
+       public int countAllByObserved(boolean observed);
+       @Query("SELECT COUNT(f) FROM Fraud f")
+       public int countAllFraud();
+}
diff --git a/Core/src/main/java/hu/user/core/service/FraudService.java b/Core/src/main/java/hu/user/core/service/FraudService.java
new file mode 100644 (file)
index 0000000..e49db04
--- /dev/null
@@ -0,0 +1,41 @@
+package hu.user.core.service;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import hu.user.common.dto.ConfusionMatrixDto;
+import hu.user.core.domain.Fraud;
+import hu.user.core.dto.ObservedValue;
+import hu.user.core.repository.FraudRepository;
+
+@Service
+public class FraudService {
+
+       @Autowired private FraudRepository fraudRepository;
+
+       public void savePredictionResult(String transactionUuid, int predictedValue, double positiveProbability,double negativeProbability) {
+               Fraud fraud = fraudRepository.getFraudByTransactionUuid(transactionUuid);
+               if (fraud != null) {
+                       fraudRepository.delete(fraud);
+               }
+               fraud = new Fraud(transactionUuid, predictedValue, positiveProbability, negativeProbability);
+               fraudRepository.save(fraud);
+       }
+
+       public ConfusionMatrixDto qualify() {
+               int truePositivePredictionNumber = fraudRepository.getNumberOfTruePositivePrediction();
+               int falsePositivePredictionNumber = fraudRepository.getNumberOfFalsePositivePrediction();
+               int trueNegativePredictionNumber = fraudRepository.getNumberOfTrueNegativePrediction();
+               int falseNegativePredictionNumber = fraudRepository.getNumberOfFalseNegativePrediction();
+               ConfusionMatrixDto confusionMatrixDto =
+                               new ConfusionMatrixDto(truePositivePredictionNumber, falsePositivePredictionNumber,
+                                               trueNegativePredictionNumber, falseNegativePredictionNumber);
+               return confusionMatrixDto;
+       }
+
+       public void deleteAllFraud() {
+               fraudRepository.deleteAll();
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/service/PredictionService.java b/Core/src/main/java/hu/user/core/service/PredictionService.java
new file mode 100644 (file)
index 0000000..80204b5
--- /dev/null
@@ -0,0 +1,69 @@
+package hu.user.core.service;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import hu.user.core.dto.ResponseFromSingleEstimator;
+import io.spring.guides.gs_producing_web_service.DetectionRequest;
+import io.spring.guides.gs_producing_web_service.GenericDetectionRequest;
+
+@Service
+public class PredictionService {
+
+       @Value("${estimatorUrl}/prediction") URI estimatorUrl;
+
+       @Autowired private RestTemplate restTemplate;
+
+       public Map<Integer, ResponseFromSingleEstimator> getPrediction(DetectionRequest detectionRequest) {
+               Map<String, Object> requestParameters = new HashMap<>();
+               requestParameters.put("card_number", detectionRequest.getCardNumber());
+               requestParameters.put("transaction_type", detectionRequest.getTransactionType());
+               requestParameters.put("timestamp", detectionRequest.getTimestamp());
+               requestParameters.put("amount", detectionRequest.getAmount());
+               requestParameters.put("currency_name", detectionRequest.getCurrencyName());
+               requestParameters.put("response_code", detectionRequest.getResponseCode());
+               requestParameters.put("country_name", detectionRequest.getCountryName());
+               requestParameters.put("vendor_code", detectionRequest.getVendorCode());
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(requestParameters, httpHeaders);
+               ParameterizedTypeReference<Map<Integer, ResponseFromSingleEstimator>> typeReference =
+                               new ParameterizedTypeReference<>() {
+                               };
+               ResponseEntity<Map<Integer,ResponseFromSingleEstimator>> responseEntity =
+                               restTemplate.exchange(estimatorUrl, HttpMethod.POST,httpEntity, typeReference);
+               Map<Integer,ResponseFromSingleEstimator> response= responseEntity.getBody();
+               return response;
+       }
+
+       public Map<Integer, ResponseFromSingleEstimator> getGenericPrediction(List<GenericDetectionRequest> requests) {
+               Map<String, Object> requestParameters = new HashMap<>();
+               requests.stream().forEach(e->requestParameters.put(e.getFieldName(),e.getValue()));
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(requestParameters, httpHeaders);
+               ParameterizedTypeReference<Map<Integer, ResponseFromSingleEstimator>> typeReference =
+                               new ParameterizedTypeReference<>() {
+                               };
+               ResponseEntity<Map<Integer,ResponseFromSingleEstimator>> responseEntity =
+                               restTemplate.exchange(estimatorUrl, HttpMethod.POST,httpEntity, typeReference);
+               Map<Integer,ResponseFromSingleEstimator> response= responseEntity.getBody();
+               return response;
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/service/StatisticService.java b/Core/src/main/java/hu/user/core/service/StatisticService.java
new file mode 100644 (file)
index 0000000..64a613e
--- /dev/null
@@ -0,0 +1,62 @@
+package hu.user.core.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.stereotype.Service;
+
+import hu.user.core.domain.Fraud;
+import hu.user.core.dto.ObservedValue;
+import hu.user.core.repository.FraudRepository;
+import io.spring.guides.gs_producing_web_service.StoreRealTransactionRequest;
+import io.spring.guides.gs_producing_web_service.StoreRequest;
+import io.spring.guides.gs_producing_web_service.StoreResult;
+import lombok.AllArgsConstructor;
+
+@Service
+@AllArgsConstructor
+public class StatisticService {
+
+       private final static String SUCCESSFUL = "successful";
+       private FraudRepository fraudRepository;
+
+       public StoreResult storeRealTransactions(StoreRealTransactionRequest storeRealTransactionRequest) {
+               int recordNumber;
+               List<Fraud> observedFrauds = new ArrayList<>();
+               List<StoreRequest> requests = storeRealTransactionRequest.getStoreRequest();
+               for (StoreRequest request : requests) {
+                       String transactionUuid = request.getTransactionId();
+                       Fraud fraud = fraudRepository.getFraudByTransactionUuid(transactionUuid);
+                       fraud.setObserved(true);
+                       fraud.setObservedValue(request.getFraud());
+                       observedFrauds.add(fraud);
+               }
+               fraudRepository.saveAll(observedFrauds);
+               StoreResult result = new StoreResult(SUCCESSFUL, observedFrauds.size());
+               return result;
+       }
+
+       public void storeObservedValues(List<ObservedValue> observedValues) {
+               List<Fraud> observedFrauds = new ArrayList<>();
+               for (ObservedValue observedValue : observedValues) {
+                       String transactionUuid = observedValue.getUuid();
+                       Fraud fraud = fraudRepository.getFraudByTransactionUuid(transactionUuid);
+                       if (fraud != null) {
+                               fraud.setObserved(true);
+                               fraud.setObservedValue(observedValue.getObservedFraud());
+                               observedFrauds.add(fraud);
+                       }
+               }
+               if (!observedFrauds.isEmpty()) {
+                       fraudRepository.saveAll(observedFrauds);
+               }
+       }
+
+       public int getAllTransaction() {
+               return fraudRepository.countAllFraud();
+       }
+
+       public int getAllObservedTransaction() {
+               return fraudRepository.countAllByObserved(true);
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/service/TrainClient.java b/Core/src/main/java/hu/user/core/service/TrainClient.java
new file mode 100644 (file)
index 0000000..09acf37
--- /dev/null
@@ -0,0 +1,33 @@
+package hu.user.core.service;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+public class TrainClient {
+
+       private static final String TRANSACTION_IDENTIFIER_FIELD_NAME_PATH = "/transaction_identifier_field_name";
+       @Value("${trainUrl}") String trainUrl;
+       @Autowired private RestTemplate restTemplate;
+       
+       public String getTransactionIdentifierFieldNameByEstimatorId(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TRANSACTION_IDENTIFIER_FIELD_NAME_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> responseFromTrainModule = restTemplate.getForEntity(urlTemplate, String.class, params);
+               String result = responseFromTrainModule.getBody();
+               return result;
+       }
+       
+       
+}
diff --git a/Core/src/main/java/hu/user/core/service/UrlBuilder.java b/Core/src/main/java/hu/user/core/service/UrlBuilder.java
new file mode 100644 (file)
index 0000000..675618b
--- /dev/null
@@ -0,0 +1,50 @@
+package hu.user.core.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class UrlBuilder {
+
+       private String url;
+       private Map<String, String> queryParams = new HashMap<>();
+
+       public String getUrl() {
+               return url;
+       }
+
+       public Map<String, String> getQueryParams() {
+               return queryParams;
+       }
+
+       public void build(String url, Map<String, String> queryValueByParamName) {
+               List<String> paramNames = new ArrayList<>();
+               List<String> paramValues = new ArrayList<>();
+               for (var entry : queryValueByParamName.entrySet()) {
+                       paramNames.add(entry.getKey());
+                       paramValues.add(entry.getValue());
+               }
+               buildUrl(url, paramNames);
+               fillQueryParams(paramNames, paramValues);
+       }
+
+       private void buildUrl(String url, List<String> queryParamNames) {
+               String urlTemplate;
+               UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url);
+               for (String queryParamName : queryParamNames) {
+                       uriComponentsBuilder = uriComponentsBuilder.queryParam(queryParamName, "{" + queryParamName + "}");
+               }
+               this.url = uriComponentsBuilder.encode().toUriString();
+       }
+
+       private void fillQueryParams(List<String> paramNames, List<String> paramValuee) {
+               Map<String, String> params = new HashMap<>();
+               for (int i = 0; i < paramNames.size(); i++) {
+                       params.put(paramNames.get(i), paramValuee.get(i));
+               }
+               this.queryParams = params;
+       }
+}
diff --git a/Core/src/main/java/hu/user/core/service/VoteModul.java b/Core/src/main/java/hu/user/core/service/VoteModul.java
new file mode 100644 (file)
index 0000000..fdd872b
--- /dev/null
@@ -0,0 +1,97 @@
+package hu.user.core.service;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.stereotype.Service;
+
+import hu.user.core.dto.ResponseFromSingleEstimator;
+import hu.user.core.estimators.EstimatorContainer;
+import io.spring.guides.gs_producing_web_service.DetectionRequest;
+import io.spring.guides.gs_producing_web_service.DetectionResponse;
+import io.spring.guides.gs_producing_web_service.GenerateDetectionRequest;
+import io.spring.guides.gs_producing_web_service.GenerateGenericDetectionRequest;
+import io.spring.guides.gs_producing_web_service.GenericDetectionRequest;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+@Service
+@Getter
+@Setter
+@Slf4j
+public class VoteModul {
+
+       private EstimatorContainer estimatorContainer;
+       private PredictionService predictionService;
+       private FraudService fraudService;
+
+       public VoteModul(EstimatorContainer estimatorContainer, PredictionService predictionService,
+                       FraudService fraudService) {
+               this.estimatorContainer = estimatorContainer;
+               this.predictionService = predictionService;
+               this.fraudService = fraudService;
+       }
+
+       public DetectionResponse voting(GenerateDetectionRequest generateDetectionRequest) {
+               DetectionRequest detectionRequest = generateDetectionRequest.getDetectionRequest();
+               Map<Integer, ResponseFromSingleEstimator> responseFromEstimator =
+                               predictionService.getPrediction(detectionRequest);
+               DetectionResponse detectionResponse = evaluate(responseFromEstimator);
+               String transactionId = generateDetectionRequest.getTransactionId();
+               fraudService.savePredictionResult(transactionId, detectionResponse.getPrediction(),
+                               detectionResponse.getPositiveProbability(), detectionResponse.getNegativeProbability());
+               log.info(
+                               "Prediction: " + detectionResponse.getPrediction() + ", positive probability: " + detectionResponse.getPositiveProbability() + ", negative probability: " + detectionResponse.getNegativeProbability());
+               return detectionResponse;
+       }
+
+       public DetectionResponse genericVoting(GenerateGenericDetectionRequest generateGenericDetectionRequest) {
+               List<GenericDetectionRequest> genericDetectionRequests = generateGenericDetectionRequest.getDetectionRequest();
+               Map<Integer, ResponseFromSingleEstimator> responseFromEstimator =
+                               predictionService.getGenericPrediction(genericDetectionRequests);
+               DetectionResponse detectionResponse = evaluate(responseFromEstimator);
+               String transactionIdentifierFieldName = estimatorContainer.getTransactionIdentifierFieldName();
+               String transactionIdentifierValue = null;
+               for (GenericDetectionRequest request : genericDetectionRequests) {
+                       if (request.getFieldName().equals(transactionIdentifierFieldName)) {
+                               transactionIdentifierValue = request.getValue();
+                       }
+               }
+               fraudService.savePredictionResult(transactionIdentifierValue, detectionResponse.getPrediction(),
+                               detectionResponse.getPositiveProbability(), detectionResponse.getNegativeProbability());
+               return detectionResponse;
+       }
+
+       private DetectionResponse evaluate(Map<Integer, ResponseFromSingleEstimator> responseFromEstimator) {
+               DetectionResponse summaryzedResponse = new DetectionResponse();
+               Map<Integer, Integer> estimatorWeightById = estimatorContainer.getEstimatorWeightById();
+               int noOKNumber = 0;
+               int oKNumber = 0;
+               double noOKProbability = 1;
+               double oKProbability = 1;
+
+               for (Map.Entry<Integer, ResponseFromSingleEstimator> entry : responseFromEstimator.entrySet()) {
+                       int estimatorId = entry.getKey();
+                       ResponseFromSingleEstimator responseFromSingleEstimator = entry.getValue();
+                       int weight = estimatorWeightById.get(estimatorId);
+                       if (responseFromSingleEstimator.getPrediction() == 1) {
+                               noOKNumber += weight;
+                               noOKProbability *= responseFromSingleEstimator.getPositiveProbability();
+                       } else {
+                               oKNumber += weight;
+                               oKProbability *= responseFromSingleEstimator.getNegativeProbability();
+                       }
+               }
+               if (oKNumber > noOKNumber) {
+                       summaryzedResponse.setPrediction(0);
+                       summaryzedResponse.setNegativeProbability(oKProbability);
+                       summaryzedResponse.setPositiveProbability(1 - oKProbability);
+               } else if (oKNumber < noOKNumber) {
+                       summaryzedResponse.setPrediction(1);
+                       summaryzedResponse.setPositiveProbability(noOKProbability);
+                       summaryzedResponse.setNegativeProbability(1 - noOKProbability);
+               }
+               return summaryzedResponse;
+       }
+}
diff --git a/Core/src/main/resources/application.properties b/Core/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/Core/src/main/resources/application.yml b/Core/src/main/resources/application.yml
new file mode 100644 (file)
index 0000000..a9bb31e
--- /dev/null
@@ -0,0 +1,51 @@
+spring:
+  profiles:
+    default: "dev"
+  application:
+    name: Core
+  datasource:
+    url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/common_fraud?serverTimezone=UTC
+    username: root
+    password: pwd
+  jpa:
+    hibernate:
+      ddl-auto: create
+logging:
+  level:
+    root: info
+  pattern:
+    console: = %d{yyyy-MM-dd HH:mm:ss} - %msg%n
+    file: =%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+  file:
+    name: /home/tomi/log/${spring.application.name}.log
+server:
+  port: 8081
+estimatorUrl: http://localhost:8083
+trainUrl: http://localhost:8085
+showProgressRepeatTime: 500
+trainLivenessRepeatTime: 100
+
+---
+spring:
+  config:
+    activate:
+      on-profile: dockerized
+#  jpa:
+#    show-sql: true
+#    generate-ddl: true
+#    hibernate:
+#      formar_sql: true
+server:
+  port: 8081
+logging:
+  pattern:
+    console: = %d{yyyy-MM-dd HH:mm:ss} - %msg%n
+    file: =%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+  file:
+    name: /opt/app/log/${spring.application.name}.log
+estimatorUrl: http://estimator:8083
+trainUrl: http://train:8085
+showProgressRepeatTime: 500
+trainLivenessRepeatTime: 100
+
+---
diff --git a/Core/src/main/resources/detection.xsd b/Core/src/main/resources/detection.xsd
new file mode 100644 (file)
index 0000000..79c726c
--- /dev/null
@@ -0,0 +1,90 @@
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://spring.io/guides/gs-producing-web-service"
+           targetNamespace="http://spring.io/guides/gs-producing-web-service" elementFormDefault="qualified">
+
+    <xs:element name="generateDetectionRequest">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="transaction_id" type="xs:string" minOccurs="1" maxOccurs="1"/>
+                <xs:element name="detection_request" type="tns:detection_request" minOccurs="1" maxOccurs="1"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="generateGenericDetectionRequest">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="detection_request" type="tns:generic_detection_request" minOccurs="1" maxOccurs="unbounded"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="storeRealTransactionRequest">
+        <xs:complexType>
+            <xs:sequence minOccurs="1" maxOccurs="unbounded">
+                <xs:element name="store_request" type="tns:store_request" minOccurs="1" maxOccurs="unbounded"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="storeRealTransactionResponse">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="store_result" type="tns:store_result" minOccurs="1" maxOccurs="1"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+
+
+    <xs:element name="generateDetectionResponse">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="detection_response" type="tns:detection_response" minOccurs="1" maxOccurs="1"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:complexType name="detection_request">
+        <xs:sequence>
+            <xs:element name="transaction_id" type="xs:string" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="card_number" type="xs:string" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="transaction_type" type="xs:int" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="timestamp" type="xs:dateTime" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="amount" type="xs:int" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="currency_name" type="xs:string" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="response_code" type="xs:int" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="country_name" type="xs:string" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="vendor_code" type="xs:string" minOccurs="1" maxOccurs="1"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="generic_detection_request">
+        <xs:sequence>
+            <xs:element name="field_name" type="xs:string" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="value" type="xs:string" minOccurs="1" maxOccurs="1"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="detection_response">
+        <xs:sequence>
+            <xs:element name="prediction" type="xs:int"/>
+            <xs:element name="negative_probability" type="xs:double"/>
+            <xs:element name="positive_probability" type="xs:double"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="store_request">
+        <xs:sequence>
+            <xs:element name="transaction_id" type="xs:string" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="fraud" type="xs:int" minOccurs="1" maxOccurs="1"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="store_result">
+        <xs:sequence>
+            <xs:element name="result" type="xs:string"/>
+            <xs:element name="record_number" type="xs:int"/>
+        </xs:sequence>
+    </xs:complexType>
+
+</xs:schema>
\ No newline at end of file
diff --git a/Core/src/test/java/hu/user/core/CoreApplicationTests.java b/Core/src/test/java/hu/user/core/CoreApplicationTests.java
new file mode 100644 (file)
index 0000000..8522a48
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.core;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CoreApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}
diff --git a/CoreCli/.mvn/wrapper/maven-wrapper.jar b/CoreCli/.mvn/wrapper/maven-wrapper.jar
new file mode 100644 (file)
index 0000000..c1dd12f
Binary files /dev/null and b/CoreCli/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/CoreCli/.mvn/wrapper/maven-wrapper.properties b/CoreCli/.mvn/wrapper/maven-wrapper.properties
new file mode 100644 (file)
index 0000000..b74bf7f
--- /dev/null
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/CoreCli/mvnw b/CoreCli/mvnw
new file mode 100755 (executable)
index 0000000..8a8fb22
--- /dev/null
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`\\unset -f command; \\command -v java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    fi
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+    if $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/CoreCli/mvnw.cmd b/CoreCli/mvnw.cmd
new file mode 100644 (file)
index 0000000..1d8ab01
--- /dev/null
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+               "$webclient = new-object System.Net.WebClient;"^
+               "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+               "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+               "}"^
+               "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+               "}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+  %JVM_CONFIG_MAVEN_PROPS% ^
+  %MAVEN_OPTS% ^
+  %MAVEN_DEBUG_OPTS% ^
+  -classpath %WRAPPER_JAR% ^
+  "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/CoreCli/pom.xml b/CoreCli/pom.xml
new file mode 100644 (file)
index 0000000..e8392d5
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.5.3</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>hu.user</groupId>
+    <artifactId>CoreCli</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>CoreCli</name>
+    <description>CoreCli</description>
+    <properties>
+        <java.version>11</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web-services</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-impl</artifactId>
+            <version>4.0.1</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.xml.bind</groupId>
+            <artifactId>jakarta.xml.bind-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jaxb</groupId>
+            <artifactId>jaxb-core</artifactId>
+            <version>4.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>hu.user</groupId>
+            <artifactId>CommonDto</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jaxb2-maven-plugin</artifactId>
+                <version>2.5.0</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.jvnet.jaxb2_commons</groupId>
+                        <artifactId>jaxb2-value-constructor</artifactId>
+                        <version>3.0</version>
+                    </dependency>
+                </dependencies>
+                <executions>
+                    <execution>
+                        <id>xjc</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <sourceType>wsdl</sourceType>
+                    <sources>
+                        <source>${project.basedir}/src/main/resources/wsdl/detection.wsdl</source>
+                    </sources>
+                    <arguments>
+                        <argument>-Xvalue-constructor</argument>
+                    </arguments>
+                    <extension>true</extension>
+                    <packageName>hu.user.corecli</packageName>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/CoreCli/src/main/java/hu/user/corecli/CoreCliApplication.java b/CoreCli/src/main/java/hu/user/corecli/CoreCliApplication.java
new file mode 100644 (file)
index 0000000..54ada8e
--- /dev/null
@@ -0,0 +1,164 @@
+package hu.user.corecli;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import hu.user.common.dto.EstimatorRegistryParameterDto;
+import hu.user.corecli.dto.ConfusionMatrix;
+import hu.user.corecli.service.CoreClient;
+import hu.user.corecli.service.EstimatorClient;
+
+@SpringBootApplication
+public class CoreCliApplication implements CommandLineRunner {
+
+       private final static String ADD_ESTIMATOR = "add_estimator";
+       private final static String GET_ESTIMATOR = "get_estimator";
+       private final static String UPDATE_ESTIMATOR_WEIGHT = "update_estimator_weight";
+       private final static String DELETE_ESTIMATOR = "delete_estimator";
+       private final static String PREDICT = "predict";
+       private final static String CONFUSION_MATRIX = "confusion_matrix";
+
+       private final static String ID = "id";
+       private final static String WEIGHT = "weight";
+
+       @Autowired EstimatorClient estimatorClient;
+       @Autowired CoreClient coreClient;
+
+       public static void main(String[] args) {
+               SpringApplication.run(CoreCliApplication.class, args);
+       }
+
+       @Override
+       public void run(String... args) throws Exception {
+               String idAsString;
+               String weigthAsString;
+               if (args.length > 0) {
+                       String command = args[0];
+                       switch (command) {
+                       case ADD_ESTIMATOR:
+                               if (args.length < 2) {
+                                       System.out.println("Estimator id is missing");
+                                       break;
+                               } else {
+                                       idAsString = args[1];
+                                       if (!isParameterInteger(idAsString, ID)) {
+                                               break;
+                                       }
+                               }
+                               if (args.length < 3) {
+                                       System.out.println("Estimator's weight is missing");
+                                       break;
+                               } else {
+                                       weigthAsString = args[2];
+                                       if (!isParameterInteger(weigthAsString, WEIGHT)) {
+                                               break;
+                                       }
+                               }
+                               List<Integer> registeredEstimatorIdsInEstimatorModul =
+                                               estimatorClient.addEstimatorInEstimatorModule(idAsString);
+                               weigthAsString = args[2];
+                               List<EstimatorRegistryParameterDto> registeredEstimatorIdAndWeigthInCoreModule =
+                                               coreClient.addEstimatorInCoreModule(idAsString, weigthAsString);
+                               printResponseFromModules(registeredEstimatorIdsInEstimatorModul,
+                                               registeredEstimatorIdAndWeigthInCoreModule);
+                               System.exit(0);
+                               break;
+                       case GET_ESTIMATOR:
+                               registeredEstimatorIdsInEstimatorModul = estimatorClient.getEstimatorInEstimatorModule();
+                               registeredEstimatorIdAndWeigthInCoreModule = coreClient.getEstimatorInCoreModule();
+                               printResponseFromModules(registeredEstimatorIdsInEstimatorModul,
+                                               registeredEstimatorIdAndWeigthInCoreModule);
+                               System.exit(0);
+                               break;
+                       case UPDATE_ESTIMATOR_WEIGHT:
+                               if (args.length < 2) {
+                                       System.out.println("Estimator id is missing");
+                                       break;
+                               } else {
+                                       idAsString = args[1];
+                                       if (!isParameterInteger(idAsString, ID)) {
+                                               break;
+                                       }
+                               }
+                               if (args.length < 3) {
+                                       System.out.println("Estimator's weight is missing");
+                                       break;
+                               } else {
+                                       weigthAsString = args[2];
+                                       if (!isParameterInteger(weigthAsString, WEIGHT)) {
+                                               break;
+                                       }
+                               }
+                               registeredEstimatorIdsInEstimatorModul = estimatorClient.getEstimatorInEstimatorModule();
+                               registeredEstimatorIdAndWeigthInCoreModule =
+                                               coreClient.modifyEstimatorWeightInCoreModule(idAsString, weigthAsString);
+                               printResponseFromModules(registeredEstimatorIdsInEstimatorModul,
+                                               registeredEstimatorIdAndWeigthInCoreModule);
+                               System.exit(0);
+                               break;
+
+                       case DELETE_ESTIMATOR:
+                               if (args.length < 2) {
+                                       System.out.println("Estimator id is missing");
+                                       break;
+                               }
+                               idAsString = args[1];
+                               if (!isParameterInteger(idAsString, ID)) {
+                                       break;
+                               }
+                               registeredEstimatorIdsInEstimatorModul = estimatorClient.deleteEstimatorInEstimatorModule(idAsString);
+                               registeredEstimatorIdAndWeigthInCoreModule = coreClient.deleteEstimatorInCoreModule(idAsString);
+                               printResponseFromModules(registeredEstimatorIdsInEstimatorModul,
+                                               registeredEstimatorIdAndWeigthInCoreModule);
+                               System.exit(0);
+                               break;
+                       case CONFUSION_MATRIX:
+                               ConfusionMatrix confusionMatrix = coreClient.getConfusionMatrix();
+                               System.out.println("True positive: " + confusionMatrix.getTruePositive());
+                               System.out.println("False positive: " + confusionMatrix.getFalsePositive());
+                               System.out.println("True negative: " + confusionMatrix.getTrueNegative());
+                               System.out.println("False negative: " + confusionMatrix.getFalseNegative());
+                               System.exit(0);
+                       default:
+                               System.out.println("Available commmands:");
+                               System.out.println(
+                                               "Register estimator in Estimator and Core module: add_estimator <estimator_id> <weight>");
+                               System.out.println(
+                                               "List registered estimator in Estimator and Core module: get_estimator");
+                               System.out.println(
+                                               "Update registered estimator's weight in Estimator and Core module: update_estimator <estimator_id> <weight>");
+                               System.out.println(
+                                               "Delete registered estimator in Estimator and Core module: delete_estimator <estimator_id>");
+                               System.out.println(
+                                               "Print system prediction quality: confusion_matrix");
+                               System.exit(0);
+                       }
+               }
+       }
+       private static boolean isParameterInteger(String parameter, String parameterName) {
+               boolean isInt = false;
+               try {
+                       Integer.parseInt(parameter);
+                       isInt = true;
+               } catch (NumberFormatException e) {
+                       System.out.printf("The %s parameter isn't integer", parameterName);
+               }
+               return isInt;
+       }
+
+       private static void printResponseFromModules(List<Integer> registeredEstimatorIdsInEstimatorModul,
+                       List<EstimatorRegistryParameterDto> registeredEstimatorIdAndWeigthInCoreModule) {
+               String ids = registeredEstimatorIdsInEstimatorModul.stream().map(e -> String.valueOf(e))
+                               .collect(Collectors.joining(",", "{", "}"));
+               System.out.println("Registered estimator ids in Estimator module: " + ids);
+               String idsAndWeights = registeredEstimatorIdAndWeigthInCoreModule.stream()
+                               .map(e -> "id: " + e.getEstimatorId() + " ; weight: " + e.getWeight())
+                               .collect(Collectors.joining("; ", "{", "}"));
+               System.out.println("Registered estimator ids and weights in Core module: " + idsAndWeights);
+       }
+}
diff --git a/CoreCli/src/main/java/hu/user/corecli/config/RestConfig.java b/CoreCli/src/main/java/hu/user/corecli/config/RestConfig.java
new file mode 100644 (file)
index 0000000..764d8cc
--- /dev/null
@@ -0,0 +1,18 @@
+package hu.user.corecli.config;
+
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestConfig {
+
+    @Bean
+    public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
+        RestTemplate restTemplate = builder
+                .build();
+        return restTemplate;
+
+    }
+}
diff --git a/CoreCli/src/main/java/hu/user/corecli/dto/ConfusionMatrix.java b/CoreCli/src/main/java/hu/user/corecli/dto/ConfusionMatrix.java
new file mode 100644 (file)
index 0000000..5859ff8
--- /dev/null
@@ -0,0 +1,38 @@
+package hu.user.corecli.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class ConfusionMatrix {
+
+       private int truePositive;
+       private int falsePositive;
+       private int trueNegative;
+       private int falseNegative;
+
+       public ConfusionMatrix(@JsonProperty(value = "true_positive", required = true) int truePositive,
+                       @JsonProperty(value = "false_positive", required = true) int falsePositive,
+                       @JsonProperty(value = "true_negative", required = true) int trueNegative,
+                       @JsonProperty(value = "false_negative", required = true) int falseNegative) {
+
+               this.truePositive = truePositive;
+               this.falsePositive = falsePositive;
+               this.trueNegative = trueNegative;
+               this.falseNegative = falseNegative;
+       }
+
+       public int getTruePositive() {
+               return truePositive;
+       }
+
+       public int getFalsePositive() {
+               return falsePositive;
+       }
+
+       public int getTrueNegative() {
+               return trueNegative;
+       }
+
+       public int getFalseNegative() {
+               return falseNegative;
+       }
+}
diff --git a/CoreCli/src/main/java/hu/user/corecli/service/CoreClient.java b/CoreCli/src/main/java/hu/user/corecli/service/CoreClient.java
new file mode 100644 (file)
index 0000000..f73cb06
--- /dev/null
@@ -0,0 +1,86 @@
+package hu.user.corecli.service;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import hu.user.common.dto.EstimatorRegistryParameterDto;
+import hu.user.corecli.dto.ConfusionMatrix;
+
+@Service
+public class CoreClient {
+
+       private static final String ESTIMATOR_PATH = "/estimator";
+       private static final String ESTIMATOR_DELETE_PATH = "/estimator/{id}";
+       private static final String CONFUSION_MATRIX_PATH = "/confusion_matrix";
+       private static final String WEIGHT = "weight";
+       private static final String ID = "id";
+       private static final String ESTIMATOR_ID = "estimator_id";
+
+       @Value("${coreUrl}") URI coreURI;
+
+       private RestTemplate restTemplate;
+
+       public CoreClient(RestTemplate restTemplate) {
+               this.restTemplate = restTemplate;
+       }
+
+       public List<EstimatorRegistryParameterDto> addEstimatorInCoreModule(String id, String weigth) {
+               Map<String, ?> input = Map.of(ESTIMATOR_ID, Integer.parseInt(id), WEIGHT, Integer.parseInt(weigth));
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> responseType = new ParameterizedTypeReference<>() {
+               };
+               List<EstimatorRegistryParameterDto> result =
+                               exchangeAsMap(ESTIMATOR_PATH, HttpMethod.POST, input, responseType, Collections.emptyMap());
+               return result;
+       }
+
+       public List<EstimatorRegistryParameterDto> getEstimatorInCoreModule() {
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> responseType = new ParameterizedTypeReference<>() {
+               };
+               List<EstimatorRegistryParameterDto> result =
+                               exchangeAsMap(ESTIMATOR_PATH, HttpMethod.GET, Collections.emptyMap(), responseType, Collections.emptyMap());
+               return result;
+       }
+
+       public List<EstimatorRegistryParameterDto> modifyEstimatorWeightInCoreModule(String id, String weigth) {
+               Map<String, ?> input = Map.of(ESTIMATOR_ID, Integer.parseInt(id), WEIGHT, Integer.parseInt(weigth));
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> responseType = new ParameterizedTypeReference<>() {
+               };
+               List<EstimatorRegistryParameterDto> result =
+                               exchangeAsMap(ESTIMATOR_PATH, HttpMethod.PUT, input, responseType, Collections.emptyMap());
+               return result;
+       }
+       public List<EstimatorRegistryParameterDto> deleteEstimatorInCoreModule(String idAsString) {
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> responseType = new ParameterizedTypeReference<>() {
+               };
+               Map<String, ?> uriParam = Map.of(ID, Integer.parseInt(idAsString));
+               List<EstimatorRegistryParameterDto> result =
+                               exchangeAsMap(ESTIMATOR_DELETE_PATH, HttpMethod.DELETE, Collections.emptyMap(), responseType, uriParam);
+               return result;
+       }
+       public ConfusionMatrix getConfusionMatrix() {
+               String url = coreURI + CONFUSION_MATRIX_PATH;
+               ConfusionMatrix confusionMatrix = restTemplate.getForObject(url, ConfusionMatrix.class);
+               return confusionMatrix;
+       }
+       private List<EstimatorRegistryParameterDto> exchangeAsMap(String path, HttpMethod httpMethod, Map<String, ?> input,
+                       ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> responseType, Map<String, ?> uriVariables) {
+               String url = coreURI + path;
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<Map<String, ?>> httpEntity = new HttpEntity<>(input, httpHeaders);
+               return restTemplate.exchange(url, httpMethod, httpEntity, responseType, uriVariables).getBody();
+       }
+}
diff --git a/CoreCli/src/main/java/hu/user/corecli/service/EstimatorClient.java b/CoreCli/src/main/java/hu/user/corecli/service/EstimatorClient.java
new file mode 100644 (file)
index 0000000..1183d58
--- /dev/null
@@ -0,0 +1,61 @@
+package hu.user.corecli.service;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class EstimatorClient {
+
+       private static final String PREDICTION_PATH = "/prediction";
+       private static final String ESTIMATOR_PATH = "/estimator";
+       private static final String ESTIMATOR_DELETE_PATH = "/estimator/{id}";
+       private static final String ID = "id";
+       private static final String ESTIMATOR_ID = "estimator_id";
+
+       @Value("${estimatorUrl}") URI estimatorURI;
+       private RestTemplate restTemplate;
+
+       public EstimatorClient(RestTemplate restTemplate) {
+               this.restTemplate = restTemplate;
+       }
+       public List<Integer> addEstimatorInEstimatorModule(String id) {
+               Map<String, ?> input = Map.of(ESTIMATOR_ID, Integer.parseInt(id));
+               ParameterizedTypeReference<List<Integer>> responseType = new ParameterizedTypeReference<>() {
+               };
+               List<Integer> result = exchangeAsList(ESTIMATOR_PATH, HttpMethod.POST, input, responseType, Collections.emptyMap());
+               return result;
+       }
+       public List<Integer> getEstimatorInEstimatorModule() {
+               ParameterizedTypeReference<List<Integer>> responseType = new ParameterizedTypeReference<>() {
+               };
+               List<Integer> result = exchangeAsList(ESTIMATOR_PATH, HttpMethod.GET, Collections.emptyMap(), responseType, Collections.emptyMap());
+               return result;
+       }
+       public List<Integer> deleteEstimatorInEstimatorModule(String idAsString) {
+               ParameterizedTypeReference<List<Integer>> responseType = new ParameterizedTypeReference<>() {
+               };
+               Map<String, ?> uriParam = Map.of(ID, Integer.parseInt(idAsString));
+               List<Integer> result = exchangeAsList(ESTIMATOR_DELETE_PATH, HttpMethod.DELETE, Collections.emptyMap(), responseType, uriParam);
+               return result;
+       }
+       private <T> List<T> exchangeAsList(String path, HttpMethod httpMethod, Map<String, ?> input,
+                       ParameterizedTypeReference<List<T>> responseType, Map<String, ?> uriVariables) {
+               String url = estimatorURI + path;
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<Map<String, ?>> httpEntity = new HttpEntity<>(input, httpHeaders);
+               return restTemplate.exchange(url, httpMethod, httpEntity, responseType, uriVariables).getBody();
+       }
+}
diff --git a/CoreCli/src/main/resources/application-mkigui.properties b/CoreCli/src/main/resources/application-mkigui.properties
new file mode 100644 (file)
index 0000000..13fee12
--- /dev/null
@@ -0,0 +1,5 @@
+server.port=8087
+estimatorUrl=http://mkigui:8083
+coreUrl=http://mkigui:8081
+spring.main.banner-mode=off
+logging.pattern.console=
\ No newline at end of file
diff --git a/CoreCli/src/main/resources/application.properties b/CoreCli/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..25eb20d
--- /dev/null
@@ -0,0 +1,4 @@
+server.port=8087
+estimatorUrl=http://localhost:8083
+coreUrl=http://localhost:8081
+
diff --git a/CoreCli/src/main/resources/wsdl/detection.wsdl b/CoreCli/src/main/resources/wsdl/detection.wsdl
new file mode 100644 (file)
index 0000000..288eca3
--- /dev/null
@@ -0,0 +1,140 @@
+<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://spring.io/guides/gs-producing-web-service" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://spring.io/guides/gs-producing-web-service" targetNamespace="http://spring.io/guides/gs-producing-web-service">
+    <wsdl:types>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://spring.io/guides/gs-producing-web-service">
+            <xs:element name="generateDetectionRequest">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="1" minOccurs="1" name="transaction_id" type="xs:string"/>
+                        <xs:element maxOccurs="1" minOccurs="1" name="detection_request" type="tns:detection_request"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="generateGenericDetectionRequest">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="unbounded" minOccurs="1" name="detection_request" type="tns:generic_detection_request"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="storeRealTransactionRequest">
+                <xs:complexType>
+                    <xs:sequence maxOccurs="unbounded" minOccurs="1">
+                        <xs:element maxOccurs="unbounded" minOccurs="1" name="store_request" type="tns:store_request"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="storeRealTransactionResponse">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="1" minOccurs="1" name="store_result" type="tns:store_result"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="generateDetectionResponse">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="1" minOccurs="1" name="detection_response" type="tns:detection_response"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:complexType name="detection_request">
+                <xs:sequence>
+                    <xs:element maxOccurs="1" minOccurs="1" name="transaction_id" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="card_number" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="transaction_type" type="xs:int"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="timestamp" type="xs:dateTime"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="amount" type="xs:int"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="currency_name" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="response_code" type="xs:int"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="country_name" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="vendor_code" type="xs:string"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="generic_detection_request">
+                <xs:sequence>
+                    <xs:element maxOccurs="1" minOccurs="1" name="field_name" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="value" type="xs:string"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="detection_response">
+                <xs:sequence>
+                    <xs:element name="prediction" type="xs:int"/>
+                    <xs:element name="negative_probability" type="xs:double"/>
+                    <xs:element name="positive_probability" type="xs:double"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="store_request">
+                <xs:sequence>
+                    <xs:element maxOccurs="1" minOccurs="1" name="transaction_id" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="fraud" type="xs:int"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="store_result">
+                <xs:sequence>
+                    <xs:element name="result" type="xs:string"/>
+                    <xs:element name="record_number" type="xs:int"/>
+                </xs:sequence>
+            </xs:complexType>
+        </xs:schema>
+    </wsdl:types>
+    <wsdl:message name="storeRealTransactionResponse">
+        <wsdl:part element="tns:storeRealTransactionResponse" name="storeRealTransactionResponse"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="storeRealTransactionRequest">
+        <wsdl:part element="tns:storeRealTransactionRequest" name="storeRealTransactionRequest"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="generateDetectionRequest">
+        <wsdl:part element="tns:generateDetectionRequest" name="generateDetectionRequest"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="generateDetectionResponse">
+        <wsdl:part element="tns:generateDetectionResponse" name="generateDetectionResponse"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="generateGenericDetectionRequest">
+        <wsdl:part element="tns:generateGenericDetectionRequest" name="generateGenericDetectionRequest"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:portType name="DetectionPort">
+        <wsdl:operation name="storeRealTransaction">
+            <wsdl:input message="tns:storeRealTransactionRequest" name="storeRealTransactionRequest"> </wsdl:input>
+            <wsdl:output message="tns:storeRealTransactionResponse" name="storeRealTransactionResponse"> </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateDetection">
+            <wsdl:input message="tns:generateDetectionRequest" name="generateDetectionRequest"> </wsdl:input>
+            <wsdl:output message="tns:generateDetectionResponse" name="generateDetectionResponse"> </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateGenericDetection">
+            <wsdl:input message="tns:generateGenericDetectionRequest" name="generateGenericDetectionRequest"> </wsdl:input>
+        </wsdl:operation>
+    </wsdl:portType>
+    <wsdl:binding name="DetectionPortSoap11" type="tns:DetectionPort">
+        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
+        <wsdl:operation name="storeRealTransaction">
+            <soap:operation soapAction=""/>
+            <wsdl:input name="storeRealTransactionRequest">
+                <soap:body use="literal"/>
+            </wsdl:input>
+            <wsdl:output name="storeRealTransactionResponse">
+                <soap:body use="literal"/>
+            </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateDetection">
+            <soap:operation soapAction=""/>
+            <wsdl:input name="generateDetectionRequest">
+                <soap:body use="literal"/>
+            </wsdl:input>
+            <wsdl:output name="generateDetectionResponse">
+                <soap:body use="literal"/>
+            </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateGenericDetection">
+            <soap:operation soapAction=""/>
+            <wsdl:input name="generateGenericDetectionRequest">
+                <soap:body use="literal"/>
+            </wsdl:input>
+        </wsdl:operation>
+    </wsdl:binding>
+    <wsdl:service name="DetectionPortService">
+        <wsdl:port binding="tns:DetectionPortSoap11" name="DetectionPortSoap11">
+            <soap:address location="http://localhost:8081/ws"/>
+        </wsdl:port>
+    </wsdl:service>
+</wsdl:definitions>
\ No newline at end of file
diff --git a/CoreCli/src/test/java/hu/user/corecli/CoreCliApplicationTests.java b/CoreCli/src/test/java/hu/user/corecli/CoreCliApplicationTests.java
new file mode 100644 (file)
index 0000000..8839d4a
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.corecli;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CoreCliApplicationTests {
+
+       @Test
+       void contextLoads() {
+       }
+
+}
diff --git a/CoreManager/Dockerfile b/CoreManager/Dockerfile
new file mode 100644 (file)
index 0000000..de10e57
--- /dev/null
@@ -0,0 +1,4 @@
+FROM adoptopenjdk:14-jre-hotspot
+WORKDIR /opt/app
+COPY target/*.jar coremanager.jar
+CMD ["java","-jar","coremanager.jar"]
\ No newline at end of file
diff --git a/CoreManager/pom.xml b/CoreManager/pom.xml
new file mode 100644 (file)
index 0000000..8a28608
--- /dev/null
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.5.3</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>hu.user</groupId>
+    <artifactId>CoreManager</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>CoreManager</name>
+    <description>CoreManager</description>
+    <properties>
+        <java.version>11</java.version>
+        <zkspringboot.version>2.5.3</zkspringboot.version>
+    </properties>
+
+    <repositories> <!--add-->
+        <repository>
+            <id>spring-snapshots</id>
+            <name>Spring Snapshots</name>
+            <url>https://repo.spring.io/snapshot</url>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>spring-milestones</id>
+            <name>Spring Milestones</name>
+            <url>https://repo.spring.io/milestone</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>ZK CE</id>
+            <name>ZK CE Repository</name>
+            <url>http://mavensync.zkoss.org/maven2</url>
+        </repository>
+        <repository>
+            <id>ZK EVAL</id>
+            <name>ZK Evaluation Repository</name>
+            <url>http://mavensync.zkoss.org/eval</url>
+        </repository>
+    </repositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web-services</artifactId>
+        </dependency>
+        <dependency> <!--add-->
+            <groupId>org.zkoss.zkspringboot</groupId>
+            <artifactId>zkspringboot-starter</artifactId>
+            <type>pom</type>
+            <version>${zkspringboot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.zkoss.zk</groupId>
+            <artifactId>zkplus</artifactId>
+            <version>9.6.0-Eval</version>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>31.0.1-jre</version>
+        </dependency>
+        <dependency>
+            <groupId>com.opencsv</groupId>
+            <artifactId>opencsv</artifactId>
+            <version>4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>hu.user</groupId>-->
+<!--            <artifactId>Core</artifactId>-->
+<!--            <version>0.0.1-SNAPSHOT</version>-->
+<!--            <scope>compile</scope>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>org.glassfish.jaxb</groupId>
+            <artifactId>jaxb-core</artifactId>
+            <version>4.0.1</version>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>org.glassfish.jaxb</groupId>-->
+<!--            <artifactId>jaxb-runtime</artifactId>-->
+<!--            <version>4.0.1</version>-->
+<!--        </dependency>-->
+<!--        &lt;!&ndash; https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>javax.xml.bind</groupId>-->
+<!--            <artifactId>jaxb-api</artifactId>-->
+<!--            <version>2.3.0</version>-->
+<!--        </dependency>-->
+<!--        &lt;!&ndash; https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-impl &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>com.sun.xml.bind</groupId>-->
+<!--            <artifactId>jaxb-impl</artifactId>-->
+<!--            <version>4.0.1</version>-->
+<!--        </dependency>-->
+        <!-- JAXB API only -->
+
+
+<!--            <dependency>-->
+<!--                <groupId>jakarta.xml.bind</groupId>-->
+<!--                <artifactId>jakarta.xml.bind-api</artifactId>-->
+<!--                <version>4.0.0</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.sun.xml.bind</groupId>
+                <artifactId>jaxb-impl</artifactId>
+                <version>4.0.1</version>
+                <scope>runtime</scope>
+            </dependency>
+        <dependency>
+            <groupId>jakarta.xml.bind</groupId>
+            <artifactId>jakarta.xml.bind-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>hu.user</groupId>
+            <artifactId>CommonDto</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jaxb2-maven-plugin</artifactId>
+                <version>2.5.0</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.jvnet.jaxb2_commons</groupId>
+                        <artifactId>jaxb2-value-constructor</artifactId>
+                        <version>3.0</version>
+                    </dependency>
+                </dependencies>
+                <executions>
+                    <execution>
+                        <id>xjc</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <sourceType>wsdl</sourceType>
+                    <sources>
+                        <source>${project.basedir}/src/main/resources/wsdl/detection.wsdl</source>
+                    </sources>
+                    <arguments>
+                        <argument>-Xvalue-constructor</argument>
+                    </arguments>
+                    <extension>true</extension>
+                    <packageName>hu.user.coremanager</packageName>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/CoreManager/src/main/java/hu/user/coremanager/CoreManagerApplication.java b/CoreManager/src/main/java/hu/user/coremanager/CoreManagerApplication.java
new file mode 100644 (file)
index 0000000..39168d6
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.coremanager;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CoreManagerApplication {
+
+       public static void main(String[] args) {
+               SpringApplication.run(CoreManagerApplication.class, args);
+       }
+
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/config/CoreSoapClientConfiguration.java b/CoreManager/src/main/java/hu/user/coremanager/config/CoreSoapClientConfiguration.java
new file mode 100644 (file)
index 0000000..b60c2f6
--- /dev/null
@@ -0,0 +1,30 @@
+package hu.user.coremanager.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.oxm.jaxb.Jaxb2Marshaller;
+
+import hu.user.coremanager.service.CoreSoapClient;
+
+@Configuration
+public class CoreSoapClientConfiguration {
+
+       @Bean
+       public Jaxb2Marshaller marshaller() {
+               Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
+               // this package must match the package in the <generatePackage> specified in
+               // pom.xml
+//             marshaller.setContextPath("com.example.consumingwebservice.wsdl");
+               marshaller.setContextPath("hu.user.coremanager");
+               return marshaller;
+       }
+
+       @Bean
+       public CoreSoapClient coreSoapClient(Jaxb2Marshaller marshaller) {
+               CoreSoapClient client = new CoreSoapClient();
+               client.setDefaultUri("http://localhost:8081/ws");
+               client.setMarshaller(marshaller);
+               client.setUnmarshaller(marshaller);
+               return client;
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/config/DevelopmentConfig.java b/CoreManager/src/main/java/hu/user/coremanager/config/DevelopmentConfig.java
new file mode 100644 (file)
index 0000000..03e1fae
--- /dev/null
@@ -0,0 +1,36 @@
+package hu.user.coremanager.config;\r
+\r
+import javax.annotation.PostConstruct;\r
+\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.Profile;\r
+import org.zkoss.lang.Library;\r
+import org.zkoss.zk.ui.WebApps;\r
+\r
+@Configuration\r
+@Profile("dev")\r
+public class DevelopmentConfig {\r
+       private static Logger logger = LoggerFactory.getLogger(DevelopmentConfig.class);\r
+\r
+       @PostConstruct\r
+       public void initDevelopmentProperties() throws Exception {\r
+               logger.info("**************************************************************");\r
+               logger.info("**** ZK-Springboot-Demo: development configuration active ****");\r
+               logger.info("**************************************************************");\r
+\r
+               //disable various caches to avoid server restarts\r
+               Library.setProperty("org.zkoss.zk.ZUML.cache", "false");\r
+               Library.setProperty("org.zkoss.zk.WPD.cache", "false");\r
+               Library.setProperty("org.zkoss.zk.WCS.cache", "false");\r
+               Library.setProperty("org.zkoss.web.classWebResource.cache", "false");\r
+               Library.setProperty("org.zkoss.util.label.cache", "false");\r
+\r
+               // enable non minified js\r
+               WebApps.getCurrent().getConfiguration().setDebugJS(true);\r
+\r
+               // enable for debugging MVVM commands and binding (very verbose)\r
+               Library.setProperty("org.zkoss.bind.DebuggerFactory.enable", "false");\r
+       }\r
+}\r
diff --git a/CoreManager/src/main/java/hu/user/coremanager/config/RestConfig.java b/CoreManager/src/main/java/hu/user/coremanager/config/RestConfig.java
new file mode 100644 (file)
index 0000000..071fdd3
--- /dev/null
@@ -0,0 +1,21 @@
+package hu.user.coremanager.config;
+
+import java.time.Duration;
+
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestConfig {
+
+    @Bean
+    public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
+        RestTemplate restTemplate = builder
+                .setReadTimeout(Duration.ofSeconds(7))
+                .build();
+        return restTemplate;
+
+    }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/ConfusionMatrixDto.java b/CoreManager/src/main/java/hu/user/coremanager/dto/ConfusionMatrixDto.java
new file mode 100644 (file)
index 0000000..63f1826
--- /dev/null
@@ -0,0 +1,19 @@
+package hu.user.coremanager.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class ConfusionMatrixDto {
+
+       private int tp;
+       private int tn;
+       private int fp;
+       private int fn;
+
+
+
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/DirectoryProperty.java b/CoreManager/src/main/java/hu/user/coremanager/dto/DirectoryProperty.java
new file mode 100644 (file)
index 0000000..ea33922
--- /dev/null
@@ -0,0 +1,18 @@
+package hu.user.coremanager.dto;
+
+import java.io.File;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class DirectoryProperty {
+
+       private String displayName;
+       private File directory;
+
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/DownStreamErrorDto.java b/CoreManager/src/main/java/hu/user/coremanager/dto/DownStreamErrorDto.java
new file mode 100644 (file)
index 0000000..eee6eb5
--- /dev/null
@@ -0,0 +1,21 @@
+package hu.user.coremanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+
+@Getter
+public class DownStreamErrorDto {
+
+       private int errorCode;
+       private String errorName;
+       private String errorMessage;
+
+       public DownStreamErrorDto(@JsonProperty(value = "error_code", required = true) int errorCode,
+                       @JsonProperty(value = "error_name", required = true) String errorName,
+                       @JsonProperty(value = "error_message", required = true) String errorMessage) {
+               this.errorCode = errorCode;
+               this.errorName = errorName;
+               this.errorMessage = errorMessage;
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/EstimatorMetricsDto.java b/CoreManager/src/main/java/hu/user/coremanager/dto/EstimatorMetricsDto.java
new file mode 100644 (file)
index 0000000..28ed470
--- /dev/null
@@ -0,0 +1,35 @@
+package hu.user.coremanager.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class EstimatorMetricsDto {
+       private int id;
+       private int tp;
+       private int tn;
+       private int fp;
+       private int fn;
+       private double sensitivity;
+       private double specifity;
+       private double accuracy;
+       private double balancedAccuracy;
+       private double prec;
+       private double recall;
+       private double ppv;
+       private double npv;
+       private double fnr;
+       private double fpr;
+       private double fdr;
+       private double _for;
+       private double f1;
+       private double f05;
+       private double f2;
+       private double mcc;
+       private double rocauc;
+       private double youden;
+
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/EstimatorParameter.java b/CoreManager/src/main/java/hu/user/coremanager/dto/EstimatorParameter.java
new file mode 100644 (file)
index 0000000..a170db3
--- /dev/null
@@ -0,0 +1,19 @@
+package hu.user.coremanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class EstimatorParameter {
+
+    private int estimatorId;
+    private int weight;
+
+    public EstimatorParameter(@JsonProperty(value = "estimator_id",required = true) int estimatorId, int weight) {
+        this.estimatorId = estimatorId;
+        this.weight = weight;
+    }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/FieldProperty.java b/CoreManager/src/main/java/hu/user/coremanager/dto/FieldProperty.java
new file mode 100644 (file)
index 0000000..f003e5f
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.coremanager.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class FieldProperty {
+
+       private String fieldName;
+       private String fieldType;
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/FileProperty.java b/CoreManager/src/main/java/hu/user/coremanager/dto/FileProperty.java
new file mode 100644 (file)
index 0000000..21085a8
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.coremanager.dto;
+
+import java.io.File;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class FileProperty {
+
+       private File file;
+       private String fileName;
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/GenericRecordCollectionDto.java b/CoreManager/src/main/java/hu/user/coremanager/dto/GenericRecordCollectionDto.java
new file mode 100644 (file)
index 0000000..f6b7068
--- /dev/null
@@ -0,0 +1,28 @@
+package hu.user.coremanager.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class GenericRecordCollectionDto {
+       private List<String> fieldTypes = new ArrayList<>();
+       private List<String> fieldNames = new ArrayList<>();
+       private List<List<String>> records = new ArrayList<>();
+
+       // @formatter:off
+       public GenericRecordCollectionDto(
+                       @JsonProperty(value = "field_types", required = true) List<String> fieldTypes,
+                       @JsonProperty(value = "field_names", required = true) List<String> fieldNames,
+                       @JsonProperty(value = "records", required = true) List<List<String>> records) {
+               this.fieldTypes = fieldTypes;
+               this.fieldNames = fieldNames;
+               this.records = records;
+       }
+       // @formatter:on
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/MetricsAndTrainTaskName.java b/CoreManager/src/main/java/hu/user/coremanager/dto/MetricsAndTrainTaskName.java
new file mode 100644 (file)
index 0000000..b7dd1f8
--- /dev/null
@@ -0,0 +1,12 @@
+package hu.user.coremanager.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class MetricsAndTrainTaskName {
+
+       private String trainTaskName;
+       private MetricsDto metricsDto;
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/MetricsDto.java b/CoreManager/src/main/java/hu/user/coremanager/dto/MetricsDto.java
new file mode 100644 (file)
index 0000000..3281aea
--- /dev/null
@@ -0,0 +1,91 @@
+package hu.user.coremanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class MetricsDto {
+
+       private int id;
+
+       private int estimatorId;
+       private int truePositive;
+       private int trueNegative;
+       private int falsePositive;
+       private int falseNegative;
+       private double accuracy;
+       private double balancedAccuracy;
+       private double sensitivity;
+       private double specifity;
+       private double prec;
+       private double recall;
+       private double ppv;
+       private double npv;
+       private double fnr;
+       private double fpr;
+       private double fdr;
+       private double _for;
+       private double f1;
+       private double f05;
+       private double f2;
+       private double mcc;
+       private double rocauc;
+       private double youden;
+
+       // @formatter:off
+       public MetricsDto(
+                       @JsonProperty(value = "id", required = true) int id,
+                       @JsonProperty(value = "estimator_id", required = true) int estimatorId,
+                       @JsonProperty(value = "TP", required = true) int truePositive,
+                       @JsonProperty(value = "TN", required = true) int trueNegative,
+                       @JsonProperty(value = "FP", required = true) int falsePositive,
+                       @JsonProperty(value = "FN", required = true) int falseNegative,
+                       @JsonProperty(value = "accuracy", required = true) double accuracy,
+                       @JsonProperty(value = "balanced_accuracy", required = true) double balancedAccuracy,
+                       @JsonProperty(value = "sensitivity", required = true) double sensitivity,
+                       @JsonProperty(value = "specificity", required = true) double specifity,
+                       @JsonProperty(value = "precision", required = true)     double prec,
+                       @JsonProperty(value = "recall", required = true) double recall,
+                       @JsonProperty(value = "PPV", required = true)   double ppv,
+                       @JsonProperty(value = "NPV", required = true)   double npv,
+                       @JsonProperty(value = "FNR", required = true) double fnr,
+                       @JsonProperty(value = "FPR", required = true) double fpr,
+                       @JsonProperty(value = "FDR", required = true) double fdr,
+                       @JsonProperty(value = "FOR", required = true) double _for,
+                       @JsonProperty(value = "f1", required = true) double f1,
+                       @JsonProperty(value = "f0.5", required = true) double f05,
+                       @JsonProperty(value = "f2", required = true) double f2,
+                       @JsonProperty(value = "MCC", required = true) double mcc,
+                       @JsonProperty(value = "ROCAUC", required = true) double rocauc,
+                       @JsonProperty(value = "Youdens_statistic", required = true) double youden)
+       // @formatter:on
+       {
+               this.id = id;
+               this.estimatorId = estimatorId;
+               this.truePositive = truePositive;
+               this.trueNegative = trueNegative;
+               this.falsePositive = falsePositive;
+               this.falseNegative = falseNegative;
+               this.accuracy = accuracy;
+               this.balancedAccuracy = balancedAccuracy;
+               this.sensitivity = sensitivity;
+               this.specifity = specifity;
+               this.prec = prec;
+               this.recall = recall;
+               this.ppv = ppv;
+               this.npv = npv;
+               this.fnr = fnr;
+               this.fpr = fpr;
+               this.fdr = fdr;
+               this._for = _for;
+               this.f1 = f1;
+               this.f05 = f05;
+               this.f2 = f2;
+               this.mcc = mcc;
+               this.rocauc = rocauc;
+               this.youden = youden;
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/dto/ObservedValue.java b/CoreManager/src/main/java/hu/user/coremanager/dto/ObservedValue.java
new file mode 100644 (file)
index 0000000..a532d9b
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.coremanager.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class ObservedValue {
+
+       private String uuid;
+       private int observedFraud;
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/exception/ParametersExistYetException.java b/CoreManager/src/main/java/hu/user/coremanager/exception/ParametersExistYetException.java
new file mode 100644 (file)
index 0000000..b9e19a9
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.coremanager.exception;
+
+public class ParametersExistYetException extends RuntimeException{
+
+       private String errorMessage;
+
+       public ParametersExistYetException(String errorMessage) {
+               this.errorMessage = errorMessage;
+       }
+
+       public String getErrorMessage() {
+               return errorMessage;
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/service/CoreClient.java b/CoreManager/src/main/java/hu/user/coremanager/service/CoreClient.java
new file mode 100644 (file)
index 0000000..61eb606
--- /dev/null
@@ -0,0 +1,137 @@
+package hu.user.coremanager.service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import hu.user.common.dto.ConfusionMatrixDto;
+import hu.user.common.dto.EstimatorRegistryParameterDto;
+import hu.user.coremanager.dto.ObservedValue;
+
+@Service
+public class CoreClient {
+
+       @Value("${coreUrl}") String coreUrl;
+       @Autowired private RestTemplate restTemplate;
+
+       private static final String ESTIMATOR_PATH = "/estimator";
+       private static final String OBSERVED_PATH = "/observed";
+       private static final String TRANSACTIONM_PATH = "/transaction";
+       private static final String CONFUSION_MATRIX_PATH = "/confusion_matrix";
+       private static final String TRANSACTION_CLEAR_PATH = "/transaction_clear";
+
+       private static final String ID = "id";
+
+       public List<EstimatorRegistryParameterDto> registryEstimator(EstimatorRegistryParameterDto estimatorRegistryParameterDto) {
+               String url = coreUrl + ESTIMATOR_PATH;
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<EstimatorRegistryParameterDto> httpEntity = new HttpEntity<>(estimatorRegistryParameterDto, httpHeaders);
+               ResponseEntity<List<EstimatorRegistryParameterDto>> responseFromCoreModul =
+                               restTemplate.exchange(url, HttpMethod.POST, httpEntity, typeReference, Collections.emptyMap());
+               List<EstimatorRegistryParameterDto> registeredEstimatorDtos = responseFromCoreModul.getBody();
+               return registeredEstimatorDtos;
+       }
+
+       public List<EstimatorRegistryParameterDto> getRegisteredEstimators() {
+               String url = coreUrl + ESTIMATOR_PATH;
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<EstimatorRegistryParameterDto> httpEntity = new HttpEntity<>(httpHeaders);
+               ResponseEntity<List<EstimatorRegistryParameterDto>> responseFromCoreModul =
+                               restTemplate.exchange(url, HttpMethod.GET, httpEntity, typeReference);
+               List<EstimatorRegistryParameterDto> registeredEstimatorDtos = responseFromCoreModul.getBody();
+               return registeredEstimatorDtos;
+       }
+
+       public List<EstimatorRegistryParameterDto> deleteEstimator(Integer id) {
+               String url = coreUrl + ESTIMATOR_PATH + "/{id}";
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> typeReference = new ParameterizedTypeReference<>() {
+               };
+
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<EstimatorRegistryParameterDto> httpEntity = new HttpEntity<>(httpHeaders);
+               ResponseEntity<List<EstimatorRegistryParameterDto>> responseFromCoreModul =
+                               restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, typeReference, Map.of(ID, id));
+               List<EstimatorRegistryParameterDto> registeredEstimatorDtos = responseFromCoreModul.getBody();
+               return registeredEstimatorDtos;
+       }
+
+       public List<EstimatorRegistryParameterDto> modifyEstimatorWeight(EstimatorRegistryParameterDto estimatorRegistryParameterDto) {
+               String url = coreUrl + ESTIMATOR_PATH;
+               ParameterizedTypeReference<List<EstimatorRegistryParameterDto>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<EstimatorRegistryParameterDto> httpEntity = new HttpEntity<>(estimatorRegistryParameterDto, httpHeaders);
+               ResponseEntity<List<EstimatorRegistryParameterDto>> responseFromCoreModul =
+                               restTemplate.exchange(url, HttpMethod.PUT, httpEntity, typeReference, Collections.emptyMap());
+               List<EstimatorRegistryParameterDto> registeredEstimatorDtos = responseFromCoreModul.getBody();
+               return registeredEstimatorDtos;
+       }
+
+       public ConfusionMatrixDto getSysTemConfusionMatrix() {
+               String url = coreUrl + CONFUSION_MATRIX_PATH;
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               ResponseEntity<ConfusionMatrixDto> responseFromCoreModul = restTemplate.getForEntity(url, ConfusionMatrixDto.class);
+               return responseFromCoreModul.getBody();
+       }
+
+       public boolean uploadToCoreModuleObservedValues(List<ObservedValue> observedValues) {
+               String url = coreUrl + OBSERVED_PATH;
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               HttpEntity<List<ObservedValue>> httpEntity = new HttpEntity<>(observedValues,httpHeaders);
+               ResponseEntity<?> responseFromCoreModul =
+                               restTemplate.exchange(url, HttpMethod.POST, httpEntity, ResponseEntity.class, Collections.emptyMap());
+               if (responseFromCoreModul.getStatusCode().is2xxSuccessful()) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+       public int getAllObservedTransaction() {
+               String url = coreUrl + OBSERVED_PATH;
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               ResponseEntity<Integer> responseFromCoreModul = restTemplate.getForEntity(url, Integer.class);
+               return responseFromCoreModul.getBody();
+       }
+       public int getAllTransaction() {
+               String url = coreUrl + TRANSACTIONM_PATH;
+               HttpHeaders httpHeaders = new HttpHeaders();
+               httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+               httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+               ResponseEntity<Integer> responseFromCoreModul = restTemplate.getForEntity(url, Integer.class);
+               return responseFromCoreModul.getBody();
+       }
+
+       public void clearAllTransaction() {
+               String url = coreUrl + TRANSACTION_CLEAR_PATH;
+               ResponseEntity<?> responseFromCoreModul = restTemplate.postForEntity(url, null, null, Collections.emptyMap());
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/service/CoreSoapClient.java b/CoreManager/src/main/java/hu/user/coremanager/service/CoreSoapClient.java
new file mode 100644 (file)
index 0000000..0e2c54e
--- /dev/null
@@ -0,0 +1,25 @@
+package hu.user.coremanager.service;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
+import org.springframework.ws.soap.client.core.SoapActionCallback;
+
+import hu.user.coremanager.DetectionResponse;
+import hu.user.coremanager.GenerateDetectionResponse;
+import hu.user.coremanager.GenerateGenericDetectionRequest;
+
+public class CoreSoapClient extends WebServiceGatewaySupport {
+
+       @Value("${coreSoapUri}") String coreSoapUri;
+
+       public DetectionResponse getResponse(GenerateGenericDetectionRequest request) {
+
+
+               GenerateDetectionResponse response = (GenerateDetectionResponse) getWebServiceTemplate()
+                               .marshalSendAndReceive(coreSoapUri, request,
+                                               new SoapActionCallback(
+                                                               "http://spring.io/guides/gs-producing-web-service/generateGenericDetectionRequest"));
+
+               return response.getDetectionResponse();
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/service/EstimatorClient.java b/CoreManager/src/main/java/hu/user/coremanager/service/EstimatorClient.java
new file mode 100644 (file)
index 0000000..b702041
--- /dev/null
@@ -0,0 +1,39 @@
+package hu.user.coremanager.service;
+
+import java.util.Arrays;
+import java.util.EmptyStackException;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import hu.user.common.dto.EstimatorRegistryParameterDto;
+import hu.user.coremanager.dto.EstimatorParameter;
+
+@Service
+public class EstimatorClient {
+
+       private static final String ID = "id";
+
+       @Value("${estimatorUrl}") String estimatorUrl;
+       @Autowired private RestTemplate restTemplate;
+
+       private static final String ESTIMATOR_PATH = "/estimator";
+
+       public List<Integer> registerEstimator(EstimatorRegistryParameterDto estimatorRegistryParameterDto) {
+               String url = estimatorUrl + ESTIMATOR_PATH;
+               int[] result = restTemplate.postForEntity(url, estimatorRegistryParameterDto, int[].class).getBody();
+               return Arrays.stream(result).boxed().collect(Collectors.toList());
+       }
+
+       public List<Integer> deleteEstimator(int id) {
+               String url = estimatorUrl + ESTIMATOR_PATH + "/{id}";
+               int[] result = restTemplate.exchange(url, HttpMethod.DELETE, null, int[].class, Map.of(ID, id)).getBody();
+               return Arrays.stream(result).boxed().collect(Collectors.toList());
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/service/EvaluationService.java b/CoreManager/src/main/java/hu/user/coremanager/service/EvaluationService.java
new file mode 100644 (file)
index 0000000..10269b8
--- /dev/null
@@ -0,0 +1,64 @@
+package hu.user.coremanager.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.stereotype.Service;
+
+import hu.user.coremanager.DetectionResponse;
+import hu.user.coremanager.GenerateGenericDetectionRequest;
+import hu.user.coremanager.GenericDetectionRequest;
+import hu.user.coremanager.dto.GenericRecordCollectionDto;
+
+@Service
+public class EvaluationService {
+       private static final String SCHEMA_NAME = "schema_name";
+       private static final String TABLE_NAME = "table_name";
+       private static final String PREDICTED_POSITIVE = "predicted_positive";
+       private static final String PREDICTED_NEGATIVE = "predicted_negative";
+
+       private TrainClient trainClient;
+       private CoreSoapClient coreSoapClient;
+
+       public EvaluationService(TrainClient trainClient, CoreSoapClient coreSoapClient) {
+
+               this.trainClient = trainClient;
+               this.coreSoapClient = coreSoapClient;
+       }
+
+       public Map<String,Integer> predictForEvaluation(String schema, String table) {
+               int positivePredictionCaseNumber = 0;
+               int negativePredictionCaseNumber = 0;
+               GenericRecordCollectionDto genericRecordCollectionDto =
+                               trainClient.getGenericRecordsForEvaluation(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table));
+               List<List<String>> records = genericRecordCollectionDto.getRecords();
+               List<String> fieldNames = genericRecordCollectionDto.getFieldNames();
+               int fieldNumber = fieldNames.size();
+               List<GenericDetectionRequest> genericDetectionRequests = new ArrayList<>();
+               for (List<String> record : records) {
+                       for (int i = 0; i < fieldNumber; i++) {
+                               GenericDetectionRequest genericDetectionRequest =
+                                               new GenericDetectionRequest(fieldNames.get(i), record.get(i));
+                               genericDetectionRequest.setFieldName(fieldNames.get(i));
+                               genericDetectionRequests.add(genericDetectionRequest);
+                       }
+                       GenerateGenericDetectionRequest generateGenericDetectionRequest =
+                                       new GenerateGenericDetectionRequest(genericDetectionRequests);
+
+                       DetectionResponse response = coreSoapClient.getResponse(generateGenericDetectionRequest);
+                       if (response.getPrediction() == 0) {
+                               negativePredictionCaseNumber++;
+                       } else {
+                               positivePredictionCaseNumber++;
+                       }
+                       System.out.println("Prediction: " + response.getPrediction());
+                       System.out.println("Positive probability: " + response.getPositiveProbability());
+                       System.out.println("Negatíve probability: " + response.getNegativeProbability());
+
+               }
+               Map<String, Integer> result = Map.of(PREDICTED_POSITIVE, positivePredictionCaseNumber, PREDICTED_NEGATIVE,
+                               negativePredictionCaseNumber);
+               return result;
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/service/FileHandler.java b/CoreManager/src/main/java/hu/user/coremanager/service/FileHandler.java
new file mode 100644 (file)
index 0000000..af718d4
--- /dev/null
@@ -0,0 +1,37 @@
+package hu.user.coremanager.service;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.stereotype.Service;
+
+import com.opencsv.CSVReader;
+import com.opencsv.CSVReaderBuilder;
+
+import hu.user.coremanager.dto.ObservedValue;
+
+@Service
+public class FileHandler {
+
+       public List<ObservedValue> readObservedValueFromCsvFile(File csvFile) {
+               List<ObservedValue> observedValues = new ArrayList<>();
+               try {
+                       FileReader filereader = new FileReader(csvFile);
+                       CSVReader csvReader = new CSVReaderBuilder(filereader).withSkipLines(1).build();
+                       List<String[]> allData = csvReader.readAll();
+                       for (String[] row : allData) {
+                               ObservedValue observedValue = new ObservedValue(row[0], Integer.valueOf(row[1]));
+                               observedValues.add(observedValue);
+                       }
+               } catch (FileNotFoundException e) {
+                       throw new RuntimeException(e);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+               return observedValues;
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/service/TrainClient.java b/CoreManager/src/main/java/hu/user/coremanager/service/TrainClient.java
new file mode 100644 (file)
index 0000000..33fdb3e
--- /dev/null
@@ -0,0 +1,239 @@
+package hu.user.coremanager.service;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import hu.user.coremanager.dto.ConfusionMatrixDto;
+import hu.user.coremanager.dto.GenericRecordCollectionDto;
+import hu.user.coremanager.dto.MetricsDto;
+import hu.user.coremanager.exception.ParametersExistYetException;
+import lombok.extern.slf4j.Slf4j;
+
+@Service
+@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Slf4j
+public class TrainClient {
+
+       private static final String OK = "OK";
+       private static final String ENCODING_AND_FEATURE_ENGINEERING_PATH = "/encoding_and_feature_engineering_plan";
+       private static final String TRAIN_TASK_PATH = "/train_task";
+       private static final String FIT_PATH = "/fit";
+       private static final String SCHEMA_PATH = "/schema";
+       private static final String TABLE_PATH = "/table";
+       private static final String FIELD_NAME_AND_TYPE_PATH = "/field_name_and_type";
+       private static final String FIELD_NAME_IN_ORIDNAL_POSITION_PATH = "/field_name_in_ordinal_position";
+       private static final String FIELD_TYPE_IN_ORIDNAL_POSITION_PATH = "/field_type_in_ordinal_position";
+       private static final String RECORD_PATH = "/record";
+       private static final String METRICS_PATH = "/metrics";
+       private static final String CPU_PATH = "/cpu";
+       private static final String ESTIMATOR_PATH = "/estimator";
+       private static final String CONFUSION_MATRIX_PATH = "/confusion_matrix";
+       private static final String LIVENESS_PATH = "/liveness";
+       private static final String TABLE_INFO_PATH = "/table_info";
+       private static final String GENERIC_RECORD_PATH = "/generic_records";
+       private static final String TRAIN_TASK_NAME_BY_ESTIMATOR_ID_PATH = "/train_task_name_by_estimator_id";
+
+       @Value("${trainUrl}") String trainUrl;
+       @Value("${home.directory}") private String homeDirectory;
+       @Autowired private RestTemplate restTemplate;
+
+//     public TrainModuleStatus testLiveness() {
+//             TrainModuleStatus trainModuleStatus = null;
+//             String url = trainUrl + LIVENESS_PATH;
+//             try {
+//                     LivenessDto livenessDto = restTemplate.getForEntity(url, LivenessDto.class).getBody();
+//                     if (livenessDto.getResponse().equals(OK)) {
+//                             trainModuleStatus = TrainModuleStatus.OK;
+//                     }
+//             } catch (ResourceAccessException exception) {
+//                     if (exception.getCause() instanceof ConnectException) {
+//                             trainModuleStatus = TrainModuleStatus.NOT_ACCESSIBLE;
+//                     }
+//                     if (exception.getCause() instanceof SocketTimeoutException) {
+//                             //TO-DO logging
+//                             trainModuleStatus = TrainModuleStatus.RESPONSE_TIMEOUT;
+//                     }
+//             }
+//             return trainModuleStatus;
+//     }
+
+       public String getHomeDirectory() {
+               return homeDirectory;
+       }
+
+       public int getCpuCoreNumber() {
+               String url = trainUrl + CPU_PATH;
+               int coreNumber = restTemplate.getForEntity(url, Integer.class).getBody();
+               return coreNumber;
+       }
+
+       public String[] getSchemas() {
+               String url = trainUrl + SCHEMA_PATH;
+               String[] schemas = restTemplate.getForEntity(url, String[].class).getBody();
+               return schemas;
+       }
+
+       public String[] getTables(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TABLE_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               String[] tables = restTemplate.getForEntity(urlTemplate, String[].class, params).getBody();
+               return tables;
+       }
+
+       public String[] getFieldNameInOrdinalPosition(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + FIELD_NAME_IN_ORIDNAL_POSITION_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               String[] tables = restTemplate.getForEntity(urlTemplate, String[].class, params).getBody();
+               return tables;
+       }
+       public String[] getFieldTypeInOrdinalPosition(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + FIELD_TYPE_IN_ORIDNAL_POSITION_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               String[] tables = restTemplate.getForEntity(urlTemplate, String[].class, params).getBody();
+               return tables;
+       }
+
+       public Map<String, String> getFieldTypeByName(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + FIELD_NAME_AND_TYPE_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<Map<String, String>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               Map<String, String> result =
+                               restTemplate.exchange(urlTemplate, HttpMethod.GET, null, typeReference, params).getBody();
+               return result;
+       }
+
+       public String getRecordsAsString(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + RECORD_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> tableProperties = restTemplate.getForEntity(urlTemplate, String.class, params);
+               String result = tableProperties.getBody();
+               return result;
+       }
+
+       public List<List<Object>> getRecords(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + RECORD_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<List<List<Object>>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               List<List<Object>> records = exchangeAsList(urlTemplate, typeReference, params);
+               return records;
+       }
+
+       public Map<String, String> getFeatureSelectors() {
+               return useCommmonQuery("/available_feature_selector");
+       }
+
+       public Map<String, String> getSamplers() {
+               return useCommmonQuery("/available_sampler");
+       }
+
+       public Map<String, String> getScalers() {
+               return useCommmonQuery("/available_scaler");
+       }
+
+       public Map<String, String> getModels() {
+               return useCommmonQuery("/available_model");
+       }
+
+       public Map<String, String> useCommmonQuery(String path) {
+               String url = trainUrl + path;
+               Map<String, String> parameters = restTemplate.getForEntity(url, Map.class).getBody();
+               return parameters;
+       }
+
+       public List<MetricsDto> getMetrics(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + METRICS_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<List<MetricsDto>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               List<MetricsDto> metrics = exchangeAsList(urlTemplate, typeReference, params);
+               return metrics;
+       }
+
+       public ConfusionMatrixDto getConfusionMatrixElements(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + CONFUSION_MATRIX_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<ConfusionMatrixDto> responseEntity =
+                               restTemplate.getForEntity(urlTemplate, ConfusionMatrixDto.class, params);
+               return responseEntity.getBody();
+       }
+
+       public GenericRecordCollectionDto getGenericRecordsForEvaluation(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + GENERIC_RECORD_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<GenericRecordCollectionDto> responseEntity =
+                               restTemplate.getForEntity(urlTemplate, GenericRecordCollectionDto.class, params);
+               return responseEntity.getBody();
+       }
+
+       public String getTrainTaskNameByEstimatorId(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TRAIN_TASK_NAME_BY_ESTIMATOR_ID_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> responseEntity =
+                               restTemplate.getForEntity(urlTemplate, String.class, params);
+               return responseEntity.getBody();
+       }
+
+
+       public <T> Integer addGenericParameters(T parameters, String path) {
+               String url = trainUrl + path;
+               try {
+                       ResponseEntity<Integer> responseFromTrainModule =
+                                       restTemplate.postForEntity(url, parameters, Integer.class);
+                       return responseFromTrainModule.getBody();
+               } catch (HttpClientErrorException httpClientErrorException) {
+                       String errorMessage = httpClientErrorException.getResponseBodyAsString();
+                       log.error(errorMessage);
+                       throw new RuntimeException(errorMessage);
+               }
+       }
+
+       private <T> List<T> exchangeAsList(String uri, ParameterizedTypeReference<List<T>> responseType,
+                       Map<String, String> uriVariables) {
+               return restTemplate.exchange(uri, HttpMethod.GET, null, responseType, uriVariables).getBody();
+       }
+
+}
+
diff --git a/CoreManager/src/main/java/hu/user/coremanager/service/UrlBuilder.java b/CoreManager/src/main/java/hu/user/coremanager/service/UrlBuilder.java
new file mode 100644 (file)
index 0000000..82fc4ae
--- /dev/null
@@ -0,0 +1,50 @@
+package hu.user.coremanager.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class UrlBuilder {
+
+       private String url;
+       private Map<String, String> queryParams = new HashMap<>();
+
+       public String getUrl() {
+               return url;
+       }
+
+       public Map<String, String> getQueryParams() {
+               return queryParams;
+       }
+
+       public void build(String url, Map<String, String> queryValueByParamName) {
+               List<String> paramNames = new ArrayList<>();
+               List<String> paramValues = new ArrayList<>();
+               for (var entry : queryValueByParamName.entrySet()) {
+                       paramNames.add(entry.getKey());
+                       paramValues.add(entry.getValue());
+               }
+               buildUrl(url, paramNames);
+               fillQueryParams(paramNames, paramValues);
+       }
+
+       private void buildUrl(String url, List<String> queryParamNames) {
+               String urlTemplate;
+               UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url);
+               for (String queryParamName : queryParamNames) {
+                       uriComponentsBuilder = uriComponentsBuilder.queryParam(queryParamName, "{" + queryParamName + "}");
+               }
+               this.url = uriComponentsBuilder.encode().toUriString();
+       }
+
+       private void fillQueryParams(List<String> paramNames, List<String> paramValuee) {
+               Map<String, String> params = new HashMap<>();
+               for (int i = 0; i < paramNames.size(); i++) {
+                       params.put(paramNames.get(i), paramValuee.get(i));
+               }
+               this.queryParams = params;
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/viewmodel/EstimatorRegistryViewModel.java b/CoreManager/src/main/java/hu/user/coremanager/viewmodel/EstimatorRegistryViewModel.java
new file mode 100644 (file)
index 0000000..5017d3f
--- /dev/null
@@ -0,0 +1,164 @@
+package hu.user.coremanager.viewmodel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.zkoss.bind.BindUtils;
+import org.zkoss.bind.annotation.BindingParam;
+import org.zkoss.bind.annotation.Command;
+import org.zkoss.bind.annotation.Init;
+import org.zkoss.bind.annotation.NotifyChange;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zul.ListModelList;
+
+import hu.user.common.dto.EstimatorRegistryParameterDto;
+import hu.user.coremanager.dto.MetricsAndTrainTaskName;
+import hu.user.coremanager.dto.MetricsDto;
+import hu.user.coremanager.service.CoreClient;
+import hu.user.coremanager.service.EstimatorClient;
+import hu.user.coremanager.service.TrainClient;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
+public class EstimatorRegistryViewModel {
+
+       private static final String ESTIMATOR_ID = "estimator_id";
+
+       @WireVariable private TrainClient trainClient;
+       @WireVariable private CoreClient coreClient;
+       @WireVariable private EstimatorClient estimatorClient;
+
+       private Integer chosenEstimatorId;
+       private List<Integer> availableWeights = new ArrayList<>();
+       private int currentWeight;
+
+       private int truePositive;
+       private int trueNegative;
+       private int falsePositive;
+       private int falseNegative;
+
+       private Map<Integer, Integer> registeredEstimatorWeightById = new HashMap<>();
+       private ListModelList<EstimatorRegistryParameterDto> estimatorRegistryParameters = new ListModelList<>();
+
+       private ListModelList<MetricsDto> metrics = new ListModelList<>();
+       private ListModelList<MetricsAndTrainTaskName> metricsAndTrainTaskNames;
+
+       private Integer selected;
+
+       private Integer chosenWeight;
+
+       private boolean registryButtonDisabled;
+       private boolean updateButtonDisabled;
+       private boolean enablePredictionButtonDisabled;
+       private boolean disablePredictionButtonDisabled;
+
+       @Init
+       public void init() {
+               registryButtonDisabled = true;
+               fillMetrics();
+               availableWeights = IntStream.rangeClosed(1, 8).boxed().collect(Collectors.toList());
+               List<EstimatorRegistryParameterDto> responseFromCore =
+                               coreClient.getRegisteredEstimators();
+               estimatorRegistryParameters = new ListModelList<>(responseFromCore);
+               if (!responseFromCore.isEmpty()) {
+                       BindUtils.postGlobalCommand(null, null, "enableTestPredictionStartButton", null);
+               }
+       }
+
+       public void fillMetrics() {
+               List<MetricsAndTrainTaskName> metricsAndTrainTaskNameCollection = new ArrayList<>();
+               List<MetricsDto> metricsDtos = trainClient.getMetrics(Collections.emptyMap());
+               for (MetricsDto metricsDto : metricsDtos) {
+                       int estimatorId = metricsDto.getEstimatorId();
+                       String trainTaskName =
+                                       trainClient.getTrainTaskNameByEstimatorId(Map.of(ESTIMATOR_ID, String.valueOf(estimatorId)));
+                       MetricsAndTrainTaskName metricsAndTrainTaskName = new MetricsAndTrainTaskName(trainTaskName, metricsDto);
+                       metricsAndTrainTaskNameCollection.add(metricsAndTrainTaskName);
+               }
+               metricsAndTrainTaskNames = new ListModelList<>(metricsAndTrainTaskNameCollection);
+       }
+
+       @Command
+       @NotifyChange({"chosenEstimatorId", "truePositive", "trueNegative", "falsePositive", "falseNegative"})
+       public void selectEstimatorId(@BindingParam("param") int selectedEstimatorId) {
+               for (MetricsAndTrainTaskName metricsAndTrantaskName : metricsAndTrainTaskNames) {
+                       if (metricsAndTrantaskName.getMetricsDto().getId() == selectedEstimatorId) {
+                               truePositive = metricsAndTrantaskName.getMetricsDto().getTruePositive();
+                               trueNegative = metricsAndTrantaskName.getMetricsDto().getTrueNegative();
+                               falsePositive = metricsAndTrantaskName.getMetricsDto().getFalsePositive();
+                               falseNegative = metricsAndTrantaskName.getMetricsDto().getFalseNegative();
+                       }
+                       if (!estimatorRegistryParameters.stream().map(e -> e.getEstimatorId()).collect(Collectors.toList())
+                                       .contains(selectedEstimatorId)) {
+                               chosenEstimatorId = selectedEstimatorId;
+                       }
+               }
+//             estimatorId = metrics.get(selected).getId();
+       }
+
+       @Command
+       @NotifyChange({"estimatorRegistryParameters", "chosenEstimatorId", "chosenWeight"})
+       public void registryEstimator() {
+               EstimatorRegistryParameterDto estimatorRegistryParameterDto =
+                               new EstimatorRegistryParameterDto(chosenEstimatorId, chosenWeight);
+               List<EstimatorRegistryParameterDto> responseFromCore =
+                               coreClient.registryEstimator(estimatorRegistryParameterDto);
+               estimatorRegistryParameters = new ListModelList<>(responseFromCore);
+               responseFromCore.stream().map(e -> "id: " + e.getEstimatorId() + " weight: " + e.getWeight())
+                               .collect(Collectors.toList()).forEach(System.out::println);
+               List<Integer> responseFromEstimator = estimatorClient.registerEstimator(estimatorRegistryParameterDto);
+               responseFromEstimator.forEach(System.out::println);
+               if (!responseFromEstimator.isEmpty()) {
+                       BindUtils.postGlobalCommand(null, null, "enableTestPredictionStartButton", null);
+               }
+               chosenEstimatorId = null;
+               chosenWeight = null;
+       }
+
+       @Command
+       public void increaseWeight(@BindingParam("param") int chosenEstimatorId) {
+               estimatorRegistryParameters.stream().filter(e -> e.getEstimatorId() == chosenEstimatorId).findFirst()
+                               .ifPresent(this::increaseEstimatorWeight);
+       }
+
+       @Command
+       public void decreaseWeight(@BindingParam("param") int chosenEstimatorId) {
+               estimatorRegistryParameters.stream().filter(e -> e.getEstimatorId() == chosenEstimatorId).findFirst()
+                               .ifPresent(this::decreaseEstimatorWeight);
+       }
+
+       private void increaseEstimatorWeight(EstimatorRegistryParameterDto parameterDto) {
+               int weigth = parameterDto.getWeight();
+               parameterDto.setWeight(weigth + 1);
+               estimatorRegistryParameters = new ListModelList<>(coreClient.modifyEstimatorWeight(parameterDto));
+               BindUtils.postNotifyChange(null, null, this, "estimatorRegistryParameters");
+       }
+
+       private void decreaseEstimatorWeight(EstimatorRegistryParameterDto parameterDto) {
+               int weigth = parameterDto.getWeight();
+               if (weigth > 1) {
+                       parameterDto.setWeight(weigth - 1);
+                       estimatorRegistryParameters = new ListModelList<>(coreClient.modifyEstimatorWeight(parameterDto));
+                       BindUtils.postNotifyChange(null, null, this, "estimatorRegistryParameters");
+               }
+       }
+
+       @Command
+       @NotifyChange("estimatorRegistryParameters")
+       public void deleteEstimator(@BindingParam("param") int chosenEstimatorId) {
+               estimatorRegistryParameters = new ListModelList<>(coreClient.deleteEstimator(chosenEstimatorId));
+               List<Integer> registeredEstimatorIdEstimatorModule = estimatorClient.deleteEstimator(chosenEstimatorId);
+               if (registeredEstimatorIdEstimatorModule.isEmpty()) {
+                       BindUtils.postGlobalCommand(null, null, "disableTestPredictionStartButton", null);
+               }
+       }
+}
diff --git a/CoreManager/src/main/java/hu/user/coremanager/viewmodel/QualifyingViewModel.java b/CoreManager/src/main/java/hu/user/coremanager/viewmodel/QualifyingViewModel.java
new file mode 100644 (file)
index 0000000..2a0c7e7
--- /dev/null
@@ -0,0 +1,217 @@
+package hu.user.coremanager.viewmodel;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.yaml.snakeyaml.events.Event;
+import org.zkoss.bind.annotation.BindingParam;
+import org.zkoss.bind.annotation.GlobalCommand;
+import org.zkoss.bind.annotation.Init;
+import org.zkoss.bind.annotation.NotifyChange;
+import org.zkoss.zk.ui.annotation.Command;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zul.Messagebox;
+
+import com.google.common.io.Files;
+
+import hu.user.common.dto.ConfusionMatrixDto;
+import hu.user.coremanager.dto.DirectoryProperty;
+import hu.user.coremanager.dto.FieldProperty;
+import hu.user.coremanager.dto.FileProperty;
+import hu.user.coremanager.dto.ObservedValue;
+import hu.user.coremanager.service.CoreClient;
+import hu.user.coremanager.service.EvaluationService;
+import hu.user.coremanager.service.FileHandler;
+import hu.user.coremanager.service.TrainClient;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
+public class QualifyingViewModel {
+
+       private static final String ID = "id";
+       private static final String TRANSACTION_IDENTIFIER = "transaction_identifier";
+       private static final String CSV = "csv";
+       private static final String SCHEMA_NAME = "schema_name";
+       private static final String TABLE_NAME = "table_name";
+       private static final String PREDICTED_POSITIVE = "predicted_positive";
+       private static final String PREDICTED_NEGATIVE = "predicted_negative";
+       private static final String REAL = "real";
+       private static final String EVALUATION = "evaluation";
+
+       @WireVariable private FileHandler fileHandler;
+       @WireVariable private CoreClient coreClient;
+       @WireVariable private TrainClient trainClient;
+       @WireVariable private EvaluationService evaluationService;
+       private String testTransactionSchema;
+       private String realTransactionSchema;
+       private List<String> schemas = new ArrayList<>();
+       private String testTransactiontable;
+       private String realTransactiontable;
+       private List<String> testTransactionTables = new ArrayList<>();
+       private List<String> realTransactionTables = new ArrayList<>();
+       private List<FieldProperty> fieldProperties = new ArrayList<>();
+       private List<File> folders = new ArrayList<>();
+       private List selectedFolders;
+       private List<File> files = new ArrayList<>();
+       private List<String> fileNames = new ArrayList<>();
+       private File selectedFolder;
+       private String fullPath;
+       private List<FileProperty> fileProperties = new ArrayList<>();
+       private List<DirectoryProperty> directoryProperties = new ArrayList<>();
+
+       private Integer selectedFileNameIndex;
+       private ConfusionMatrixDto confusionMatrixDto;
+
+       private Integer positivePredictionResultForEvaluation;
+       private Integer negativePredictionResultForEvaluation;
+       private String homeDirectory;
+       private boolean disabledTestPredictionStartButton;
+
+       @Init
+       public void init() {
+               disabledTestPredictionStartButton = true;
+               schemas = Arrays.stream(trainClient.getSchemas()).collect(Collectors.toList());
+               homeDirectory = trainClient.getHomeDirectory();
+               File root = new File(homeDirectory);
+               directoryProperties = getDirectoryProperties(root);
+               fullPath = root.getAbsolutePath();
+       }
+
+       public String getExtensionByGuava(String filename) {
+               return Files.getFileExtension(filename);
+       }
+
+       @Command
+       @NotifyChange("testTransactionTables")
+       public void changeTestTransactionSchema() {
+               if (testTransactionSchema != null && schemas.contains(testTransactionSchema)) {
+                       String[] tablesOfSelectedSchema = trainClient.getTables(Map.of(SCHEMA_NAME, testTransactionSchema));
+                       testTransactionTables = Arrays.stream(tablesOfSelectedSchema).filter(e -> e.contains(EVALUATION))
+                                       .collect(Collectors.toList());
+               }
+       }
+
+       @Command
+       @NotifyChange("realTransactionTables")
+       public void changeRealTransactionSchema() {
+               if (realTransactionSchema != null && schemas.contains(realTransactionSchema)) {
+                       String[] tablesOfSelectedSchema = trainClient.getTables(Map.of(SCHEMA_NAME, realTransactionSchema));
+                       realTransactionTables =
+                                       Arrays.stream(tablesOfSelectedSchema).filter(e -> e.contains(REAL)).collect(Collectors.toList());
+               }
+       }
+
+       @Command
+       @NotifyChange("fieldProperties")
+       public void changeRealTransactionTable() {
+               Map<String, String> fieldTypeByName = trainClient.getFieldTypeByName(
+                               Map.of(SCHEMA_NAME, realTransactionSchema, TABLE_NAME, realTransactiontable));
+               fieldProperties = fieldTypeByName.entrySet().stream().map(e -> new FieldProperty(e.getKey(), e.getValue()))
+                               .collect(Collectors.toList());
+       }
+
+       @Command
+       @NotifyChange({"positivePredictionResultForEvaluation", "negativePredictionResultForEvaluation"})
+       public void predictForEvaluation() {
+               positivePredictionResultForEvaluation = null;
+               negativePredictionResultForEvaluation = null;
+               Map<String, Integer> resultFromCoreModule =
+                               evaluationService.predictForEvaluation(testTransactionSchema, testTransactiontable);
+               positivePredictionResultForEvaluation = resultFromCoreModule.get(PREDICTED_POSITIVE);
+               negativePredictionResultForEvaluation = resultFromCoreModule.get(PREDICTED_NEGATIVE);
+       }
+
+       @Command
+       public void uploadRealTransactionFromCsvFile(@BindingParam("param") File selectedFile) {
+               List<ObservedValue> observedValues = fileHandler.readObservedValueFromCsvFile(selectedFile);
+               coreClient.uploadToCoreModuleObservedValues(observedValues);
+       }
+
+       @Command
+       public void uploadRealTransactionFromDatabase() {
+               List<List<Object>> recordsInDatabase =
+                               trainClient.getRecords(Map.of(SCHEMA_NAME, realTransactionSchema, TABLE_NAME, realTransactiontable));
+               String[] fieldNamesInArray = trainClient.getFieldNameInOrdinalPosition(
+                               Map.of(SCHEMA_NAME, realTransactionSchema, TABLE_NAME, realTransactiontable));
+               List<String> fieldNames = Arrays.stream(fieldNamesInArray).collect(Collectors.toList());
+               int idIndex = fieldNames.indexOf(ID);
+               int transactionIdentifierIndex = fieldNames.indexOf(TRANSACTION_IDENTIFIER);
+               int fieldNameNumber = fieldNames.size();
+               List<ObservedValue> observedValues = new ArrayList<>();
+               for (List<Object> record : recordsInDatabase) {
+                       ObservedValue observedValue = new ObservedValue(String.valueOf(record.get(transactionIdentifierIndex)),
+                                       (Integer) record.get(fieldNameNumber - 1));
+                       observedValues.add(observedValue);
+               }
+               coreClient.uploadToCoreModuleObservedValues(observedValues);
+               Messagebox.show("Real transaction has been uploaded.");
+       }
+
+       @Command
+       @NotifyChange({"directoryProperties", "fileProperties", "fullPath"})
+       public void changeDirectory(@BindingParam("param") String selectedDirectoryName) {
+               File selectedDirectory = new File(selectedDirectoryName);
+               directoryProperties.clear();
+               directoryProperties.add(new DirectoryProperty("..", selectedDirectory.getParentFile()));
+               directoryProperties.addAll(getDirectoryProperties(selectedDirectory));
+               fileProperties = getFileProperties(selectedDirectory);
+               fullPath = selectedDirectoryName;
+       }
+
+       @Command
+       @NotifyChange("fileProperties")
+       public void displayFiles() {
+               System.out.println(selectedFolder);
+       }
+
+       @Command
+       @NotifyChange("confusionMatrixDto")
+       public void refreshSystemConfusionMatrix() {
+               confusionMatrixDto = coreClient.getSysTemConfusionMatrix();
+       }
+
+       @GlobalCommand
+       @NotifyChange("disabledTestPredictionStartButton")
+       public void enableTestPredictionStartButton() {
+               System.out.println("enable");
+               disabledTestPredictionStartButton = false;
+       }
+
+       @GlobalCommand
+       @NotifyChange("disabledTestPredictionStartButton")
+       public void disableTestPredictionStartButton() {
+               System.out.println("disable");
+               disabledTestPredictionStartButton = true;
+       }
+
+       @Command
+       @NotifyChange("confusionMatrixDto")
+       public void clearAllTransaction() {
+               coreClient.clearAllTransaction();
+               confusionMatrixDto = null;
+       }
+
+       private List<File> getDirectories(File file) {
+               return Arrays.stream(file.listFiles()).filter(e -> e.isDirectory() && e.isHidden() == false)
+                               .collect(Collectors.toList());
+       }
+
+       private List<DirectoryProperty> getDirectoryProperties(File directory) {
+               return Arrays.stream(directory.listFiles()).filter(e -> e.isDirectory() && !e.isHidden())
+                               .map(e -> new DirectoryProperty(e.getName(), new File(e.toURI()))).collect(Collectors.toList());
+       }
+
+       private List<FileProperty> getFileProperties(File directory) {
+               return Arrays.stream(directory.listFiles())
+                               .filter(e -> e.isFile() && getExtensionByGuava(e.getName()).equals(CSV))
+                               .map(e -> new FileProperty(e, e.getName())).collect(Collectors.toList());
+       }
+}
diff --git a/CoreManager/src/main/resources/META-INF/spring-devtools.properties b/CoreManager/src/main/resources/META-INF/spring-devtools.properties
new file mode 100644 (file)
index 0000000..0161707
--- /dev/null
@@ -0,0 +1 @@
+restart.include.zklibs=/z[\\w]+-[\\w\\d-\.]+\.jar
diff --git a/CoreManager/src/main/resources/application.properties b/CoreManager/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..23cad3e
--- /dev/null
@@ -0,0 +1,4 @@
+zk.homepage=start
+zk.zul-view-resolver-prefix=/zul
+zk.resource-uri=/zkres
+
diff --git a/CoreManager/src/main/resources/application.yml b/CoreManager/src/main/resources/application.yml
new file mode 100644 (file)
index 0000000..21e9e2b
--- /dev/null
@@ -0,0 +1,55 @@
+spring:
+  profiles:
+    default: "dev"
+  application:
+    name: CoreManager
+  datasource:
+    url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/common_fraud?serverTimezone=UTC
+    username: root
+    password: pwd
+logging:
+  level:
+    root: info
+  pattern:
+    console: = %d{yyyy-MM-dd HH:mm:ss} - %msg%n
+    file: =%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+  file:
+    name: /home/tomi/log/${spring.application.name}.log
+server:
+  port: 8082
+trainUrl: http://localhost:8085
+estimatorUrl: http://localhost:8083
+coreUrl: http://localhost:8081
+coreSoapUri: http://localhost:8081/ws/detection
+home:
+  directory: /home/tomi
+
+---
+spring:
+  config:
+    activate:
+      on-profile: dockerized
+#  datasource:
+#    url: ${DATASOURCE_URL}
+#    username: ${MYSQL_USER}
+#    password: ${MYSQL_PASSWORD}
+#  jpa:
+#    show-sql: true
+#    generate-ddl: true
+#    hibernate:
+#      ddl-auto: create
+#      formar_sql: true
+logging:
+  pattern:
+    console: = %d{yyyy-MM-dd HH:mm:ss} - %msg%n
+    file: =%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+  file:
+    name: /opt/app/log/${spring.application.name}.log
+server:
+  port: 8082
+trainUrl: http://train:8085
+estimatorUrl: http://estimator:8083
+coreUrl: http://core:8081
+coreSoapUri: http://core:8081/ws/detection
+
+---
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/maven/generate b/CoreManager/src/main/resources/maven/generate
new file mode 100644 (file)
index 0000000..6cf2d31
--- /dev/null
@@ -0,0 +1 @@
+mvn jaxb2:xjc
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/metainfo.zk/lang-addon.xml b/CoreManager/src/main/resources/metainfo.zk/lang-addon.xml
new file mode 100644 (file)
index 0000000..02e4b61
--- /dev/null
@@ -0,0 +1,10 @@
+<language-addon>
+    <addon-name>zkspringboot-demo-addon</addon-name>
+    <version>1.0</version>
+    <language-name>xul/html</language-name>
+
+    <depends>zkmax</depends>
+
+    <stylesheet href="/css/static-globalstyles.css" type="text/css"/>
+    <stylesheet href="~./css/web-globalstyles.css" type="text/css"/>
+</language-addon>
diff --git a/CoreManager/src/main/resources/metainfo.zk/zk.xml b/CoreManager/src/main/resources/metainfo.zk/zk.xml
new file mode 100644 (file)
index 0000000..947a3a6
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<zk>
+    <config-name>zkspringboot-demo</config-name>
+    <library-property>
+        <name>org.zkoss.zul.nativebar</name>
+        <value>true</value>
+    </library-property>
+
+    <!--enable websocket support in ZK 8.5.1 (ZK-3799)  -->
+    <!--    <listener>-->
+    <!--        <listener-class>org.zkoss.zkmax.au.websocket.WebSocketWebAppInit</listener-class>-->
+    <!--    </listener>-->
+
+    <!--    <richlet>-->
+    <!--        <richlet-name>ExampleRichlet</richlet-name>&lt;!&ndash; your preferred name &ndash;&gt;-->
+    <!--        <richlet-class>org.zkoss.zkspringboot.demo.richlet.ExampleRichlet</richlet-class>&lt;!&ndash; your class name, of course &ndash;&gt;-->
+    <!--    </richlet>-->
+    <!--    <richlet-mapping>-->
+    <!--        <richlet-name>ExampleRichlet</richlet-name>-->
+    <!--        <url-pattern>/richlet/example</url-pattern>-->
+    <!--    </richlet-mapping>-->
+</zk>
diff --git a/CoreManager/src/main/resources/static/css/static-globalstyles.css b/CoreManager/src/main/resources/static/css/static-globalstyles.css
new file mode 100644 (file)
index 0000000..aaf6083
--- /dev/null
@@ -0,0 +1,6 @@
+.my-box2 {
+    border: 3px solid green;
+    padding: 15px;
+    display: block;
+    width: max-content;
+}
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/static/css/static-localstyles.css b/CoreManager/src/main/resources/static/css/static-localstyles.css
new file mode 100644 (file)
index 0000000..264982f
--- /dev/null
@@ -0,0 +1,4 @@
+.my-button {
+    color: Maroon;
+    font-weight: bold;
+}
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/static/img/zklogo1.png b/CoreManager/src/main/resources/static/img/zklogo1.png
new file mode 100644 (file)
index 0000000..24bc55b
Binary files /dev/null and b/CoreManager/src/main/resources/static/img/zklogo1.png differ
diff --git a/CoreManager/src/main/resources/web/css/airplane_background.jpg b/CoreManager/src/main/resources/web/css/airplane_background.jpg
new file mode 100644 (file)
index 0000000..1e88ac3
Binary files /dev/null and b/CoreManager/src/main/resources/web/css/airplane_background.jpg differ
diff --git a/CoreManager/src/main/resources/web/css/mySpecial.css b/CoreManager/src/main/resources/web/css/mySpecial.css
new file mode 100644 (file)
index 0000000..c378172
--- /dev/null
@@ -0,0 +1,4 @@
+.bg {
+    background-image: url("plane.jpg");
+    background-size: cover;
+}
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/web/css/plane.jpg b/CoreManager/src/main/resources/web/css/plane.jpg
new file mode 100644 (file)
index 0000000..9474f6b
Binary files /dev/null and b/CoreManager/src/main/resources/web/css/plane.jpg differ
diff --git a/CoreManager/src/main/resources/web/css/style.css b/CoreManager/src/main/resources/web/css/style.css
new file mode 100644 (file)
index 0000000..cdf5559
--- /dev/null
@@ -0,0 +1,5 @@
+.z-window-header{
+    /*background-color: aqua;*/
+    font-size: 40px;
+}
+
diff --git a/CoreManager/src/main/resources/web/img/airplane_background.jpg b/CoreManager/src/main/resources/web/img/airplane_background.jpg
new file mode 100644 (file)
index 0000000..1e88ac3
Binary files /dev/null and b/CoreManager/src/main/resources/web/img/airplane_background.jpg differ
diff --git a/CoreManager/src/main/resources/web/img/logo.png b/CoreManager/src/main/resources/web/img/logo.png
new file mode 100644 (file)
index 0000000..b7eaee5
Binary files /dev/null and b/CoreManager/src/main/resources/web/img/logo.png differ
diff --git a/CoreManager/src/main/resources/web/img/zklogo3.png b/CoreManager/src/main/resources/web/img/zklogo3.png
new file mode 100644 (file)
index 0000000..24bc55b
Binary files /dev/null and b/CoreManager/src/main/resources/web/img/zklogo3.png differ
diff --git a/CoreManager/src/main/resources/web/zul/estimatorRegistry.zul b/CoreManager/src/main/resources/web/zul/estimatorRegistry.zul
new file mode 100644 (file)
index 0000000..ad417a7
--- /dev/null
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
+<zk>
+    <borderlayout viewModel="@id('vm') @init('hu.user.coremanager.viewmodel.EstimatorRegistryViewModel')"
+                  vflex="min" width="100%">
+        <north vflex="min">
+            <listbox vflex="1" model="@load(vm.metricsAndTrainTaskNames)" mold="paging" pageSize="3"
+                     sizedByContent="true" emptyMessage="There aren't any trained estimator">
+                <listhead>
+                    <listheader label="SELECT" align="center"/>
+                    <listheader label="Train Task name" align="center"/>
+                    <listheader label="Estimator Id" align="center"/>
+                    <listheader label="accuracy" align="center"/>
+                    <listheader label="balanced accuracy" align="center"/>
+                    <listheader label="sensitivity" align="center"/>
+                    <listheader label="specifity" align="center"/>
+                    <listheader label="prec" align="center"/>
+                    <listheader label="recall" align="center"/>
+                    <listheader label="PPV" align="center"/>
+                    <listheader label="NPV" align="center"/>
+                    <listheader label="FNR" align="center"/>
+                    <listheader label="FPR" align="center"/>
+                    <listheader label="FDR" align="center"/>
+                    <listheader label="FOR" align="center"/>
+                    <listheader label="f1" align="center"/>
+                    <listheader label="f0.5" align="center"/>
+                    <listheader label="f2" align="center"/>
+                    <listheader label="MCC" align="center"/>
+                    <listheader label="ROCAUC" align="center"/>
+                    <listheader label="Youden" align="center"/>
+                </listhead>
+                <template name="model">
+                    <listitem>
+                        <listcell>
+                            <button iconSclass="z-icon-plus"
+                                    onClick="@command('selectEstimatorId',param=each.metricsDto.estimatorId)"/>
+                        </listcell>
+                        <listcell label="@load(each.trainTaskName)"/>
+                        <listcell label="@load(each.metricsDto.estimatorId)"/>
+                        <listcell label="@load(each.metricsDto.accuracy)"/>
+                        <listcell label="@load(each.metricsDto.balancedAccuracy)"/>
+                        <listcell label="@load(each.metricsDto.sensitivity)"/>
+                        <listcell label="@load(each.metricsDto.specifity)"/>
+                        <listcell label="@load(each.metricsDto.prec)"/>
+                        <listcell label="@load(each.metricsDto.recall)"/>
+                        <listcell label="@load(each.metricsDto.ppv)"/>
+                        <listcell label="@load(each.metricsDto.npv)"/>
+                        <listcell label="@load(each.metricsDto.fnr)"/>
+                        <listcell label="@load(each.metricsDto.fpr)"/>
+                        <listcell label="@load(each.metricsDto.fdr)"/>
+                        <listcell label="@load(each.metricsDto._for)"/>
+                        <listcell label="@load(each.metricsDto.f1)"/>
+                        <listcell label="@load(each.metricsDto.f05)"/>
+                        <listcell label="@load(each.metricsDto.f2)"/>
+                        <listcell label="@load(each.metricsDto.mcc)"/>
+                        <listcell label="@load(each.metricsDto.rocauc)"/>
+                        <listcell label="@load(each.metricsDto.youden)"/>
+                    </listitem>
+                </template>
+            </listbox>
+        </north>
+        <east vflex="1" width="35%">
+            <grid model="@load(vm.estimatorRegistryParameters)" vflex="1" sizedByContent="true">
+                <auxhead>
+                    <auxheader colspan="4" label="Registered Estimators" align="center"/>
+                </auxhead>
+                <columns>
+                    <column hflex="1" label="Estimator Id" align="center"/>
+                    <column hflex="1" label="Weight" align="center"/>
+                    <column hflex="1" label="Modify" align="center"/>
+                    <column hflex="1" label="Delete" align="center"/>
+                </columns>
+                <template name="model">
+                    <row>
+                        <label value="@load(each.estimatorId)"/>
+                        <label value="@load(each.weight)"/>
+                        <cell>
+                            <hbox align="center" pack="center">
+                                <button iconSclass="z-icon-plus"
+                                        onClick="@command('increaseWeight',param=each.estimatorId)"/>
+                                <button iconSclass="z-icon-minus"
+                                        onClick="@command('decreaseWeight',param=each.estimatorId))"/>
+                            </hbox>
+                        </cell>
+                        <button iconSclass="z-icon-trash-o"
+                                onClick="@command('deleteEstimator',param=each.estimatorId)"/>
+                    </row>
+                </template>
+            </grid>
+        </east>
+        <center>
+            <grid>
+                <auxhead>
+                    <auxheader colspan="2" label="Register Estimator" align="center"/>
+                </auxhead>
+                <columns>
+                    <column hflex="1"/>
+                    <column hflex="1"/>
+                </columns>
+                <rows>
+                    <row>
+                        <cell>
+                            <label hflex="1" value="Estimator Id" style="text-align: center"/>
+                        </cell>
+                        <cell>
+                            <textbox hflex="1" value="@bind(vm.chosenEstimatorId)" style="text-align: center"/>
+                        </cell>
+                    </row>
+                    <row>
+                        <cell>
+                            <label hflex="1" value="Weight"/>
+                        </cell>
+                        <cell>
+                            <spinner hflex="1"
+                                     constraint="no negative,no empty,no zero, min 1 max 10"
+                                     value="@bind(vm.chosenWeight)"
+                                     style="text-align: center"/>
+                        </cell>
+                    </row>
+                    <row>
+                        <cell/>
+                        <cell>
+                            <button hflex="1" label="REGISTER"
+                                    disabled="@load(vm.chosenEstimatorId eq null or vm.chosenWeight eq null)"
+                                    onClick="@command('registryEstimator')"/>
+                        </cell>
+                    </row>
+                </rows>
+            </grid>
+        </center>
+        <west width="40%" vflex="1">
+            <grid>
+                <auxhead>
+                    <auxheader colspan="3" label="Confusion Matrix" align="center"/>
+                </auxhead>
+
+                <columns>
+                    <column hflex="1"></column>
+                    <column hflex="1">Predicted positive</column>
+                    <column hflex="1">Predicted negative</column>
+
+                </columns>
+                <rows>
+                    <row align="center">
+                        <cell style="background-color:#0093F9 ;color:white; border-left: 1px solid #0064ED; border-top: 1px solid #0064ED;">
+                            <label>Observed positive</label>
+                        </cell>
+                        <cell>
+                            <label value="@load(vm.truePositive)"/>
+                        </cell>
+                        <cell>
+                            <label value="@load(vm.falseNegative)"/>
+                        </cell>
+                    </row>
+                    <row align="center">
+                        <cell style="background-color:#0093F9 ;color:white; border-left: 1px solid #0064ED; border-top: 1px solid #0064ED;">
+                            <label>Observed negative</label>
+                        </cell>
+                        <cell>
+                            <label value="@load(vm.falsePositive)"/>
+                        </cell>
+                        <cell>
+                            <label value="@load(vm.trueNegative)"/>
+                        </cell>
+                    </row>
+                </rows>
+            </grid>
+        </west>
+    </borderlayout>
+</zk>
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/web/zul/qualifying.zul b/CoreManager/src/main/resources/web/zul/qualifying.zul
new file mode 100644 (file)
index 0000000..1900f59
--- /dev/null
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
+<zk>
+    <borderlayout height="400px" viewModel="@id('vm') @init('hu.user.coremanager.viewmodel.QualifyingViewModel')">
+        <west width="20%" splittable="true" collapsible="true">
+            <vbox>
+                <grid>
+                    <auxhead>
+                        <auxheader colspan="2" label="Test Prediction" align="center"/>
+                    </auxhead>
+                    <columns>
+                        <column hflex="1"/>
+                        <column hflex="1"/>
+                    </columns>
+                    <rows>
+                        <row>
+                            <cell>
+                                <label value="Schema"/>
+                            </cell>
+                            <cell>
+                                <combobox hflex="1" model="@load(vm.schemas)" selectedItem="@bind(vm.testTransactionSchema)"
+                                          onChange="@command('changeTestTransactionSchema')" placeholder="choose schema"
+                                          style="text-align:center">
+                                    <template name="model">
+                                        <comboitem label="@load(each)"/>
+                                    </template>
+                                </combobox>
+
+                            </cell>
+                        </row>
+                        <row>
+                            <cell>
+                                <label value="Table"/>
+                            </cell>
+                            <cell>
+                                <combobox hflex="1" model="@load(vm.testTransactionTables)" selectedItem="@bind(vm.testTransactiontable)"
+                                          placeholder="choose table" style="text-align:center">
+                                    <template name="model">
+                                        <comboitem label="@load(each)"/>
+                                    </template>
+                                </combobox>
+                            </cell>
+                        </row>
+                        <row>
+                            <cell>
+                                <label value="Prediction"/>
+                            </cell>
+                            <cell>
+                                <button disabled="@load(vm.disabledTestPredictionStartButton or empty vm.testTransactiontable)" label="START" onClick="@command('predictForEvaluation')"/>
+                            </cell>
+                        </row>
+                    </rows>
+                </grid>
+                <grid visible="@load(not empty vm.positivePredictionResultForEvaluation)">
+                    <auxhead>
+                        <auxheader colspan="2" label="Test result" align="center"/>
+                    </auxhead>
+                    <columns>
+                        <column hflex="1"/>
+                        <column hflex="1"/>
+                    </columns>
+                    <rows>
+                        <row>
+                            <cell>
+                                <label value="Positive"/>
+                            </cell>
+                            <textbox hflex="1" value="@load(vm.positivePredictionResultForEvaluation)" readonly="true"
+                                     placeholder="predicted positive" style="text-align:center"/>
+                        </row>
+                        <row>
+                            <cell>
+                                <label value="Negative"/>
+                            </cell>
+                            <cell>
+                                <textbox hflex="1" value="@load(vm.negativePredictionResultForEvaluation)"
+                                         readonly="true" placeholder="predicted negative" style="text-align:center"/>
+                            </cell>
+                        </row>
+                    </rows>
+                </grid>
+            </vbox>
+
+        </west>
+        <east autoscroll="true" width="40%" splittable="true" collapsible="true">
+            <grid>
+                <auxhead>
+                    <auxheader colspan="3" label="PREDICTION QUALITY" align="center"/>
+                </auxhead>
+                <columns>
+                    <column hflex="1"></column>
+                    <column hflex="1">Predicted positive</column>
+                    <column hflex="1">Predicted negative</column>
+                </columns>
+                <rows>
+                    <row>
+                        <cell style="background-color:#0093F9 ;color:white; border-left: 1px solid #0064ED; border-top: 1px solid #0064ED;">
+                            <label style="color:#FFFFFF;background-color:#0093F9">Observed positive</label>
+                        </cell>
+                        <cell align="center">
+                            <label visible="@load(vm.confusionMatrixDto ne null)" value="@load(vm.confusionMatrixDto.truePositive)"/>
+                        </cell>
+                        <cell align="center">
+                            <label visible="@load(vm.confusionMatrixDto ne null)" value="@load(vm.confusionMatrixDto.falseNegative)"/>
+                        </cell>
+                    </row>
+                    <row>
+                        <cell style="background-color:#0093F9 ;color:white; border-left: 1px solid #0064ED; border-top: 1px solid #0064ED;">
+                            <label>Observed negative</label>
+                        </cell>
+                        <cell align="center">
+                            <label visible="@load(vm.confusionMatrixDto ne null)" value="@load(vm.confusionMatrixDto.falsePositive)"/>
+                        </cell>
+                        <cell align="center">
+                            <label visible="@load(vm.confusionMatrixDto ne null)" value="@load(vm.confusionMatrixDto.trueNegative)"/>
+                        </cell>
+                    </row>
+                    <row>
+                        <cell/>
+                        <cell>
+                        </cell>
+                        <cell>
+                            <button hflex="1" iconSclass="z-icon-trash" onClick="@command('clearAllTransaction')"/>
+                            <button hflex="1" iconSclass="z-icon-refresh" onClick="@command('refreshSystemConfusionMatrix')"/>
+                        </cell>
+                    </row>
+                </rows>
+            </grid>
+        </east>
+        <center autoscroll="true">
+            <tabbox vflex="1" mold="accordion">
+                <tabs>
+                    <tab label="Upload real transaction from CSV file"/>
+                    <tab label="Upload real transaction from database"/>
+                </tabs>
+                <tabpanels>
+                    <tabpanel>
+                        <borderlayout vflex="1">
+                            <north>
+                                <grid>
+                                    <auxhead>
+                                        <auxheader>
+                                            <hbox align="center">
+<!--                                                <label hflex="1" value="CSV File upload"/>-->
+                                                <hbox hflex="1" align="center">
+                                                    <label value="Path: "/>
+                                                    <textbox hflex="min" value="@load(vm.fullPath)"/>
+                                                </hbox>
+
+                                            </hbox>
+
+                                        </auxheader>
+                                    </auxhead>
+
+                                    <columns>
+                                        <column hflex="1"/>
+                                    </columns>
+                                </grid>
+                            </north>
+                            <west hflex="1">
+                                <listbox model="@load(vm.directoryProperties)" vflex="true">
+                                    <listhead>
+                                        <listheader label="Directory"/>
+                                    </listhead>
+                                    <template name="model">
+                                        <listitem>
+                                            <listcell label="@load(each.getDisplayName())"
+                                                      onClick="@command('changeDirectory',param=each.directory)"/>
+                                        </listitem>
+                                    </template>
+                                </listbox>
+                            </west>
+                            <center hflex="1">
+                                <listbox model="@load(vm.fileProperties)" mold="paging" pageSize="4" vflex="true"
+                                         selectedIndex="@bind(vm.selectedFileNameIndex)">
+                                    <listhead>
+                                        <listheader label="File name" align="center"/>
+                                        <listheader label="UPLOAD" align="center"/>
+                                    </listhead>
+                                    <template name="model">
+                                        <listitem>
+                                            <listcell label="@load(each.fileName)"/>
+                                            <listcell>
+                                                <button iconSclass="z-icon-plus"
+                                                        onClick="@command('uploadRealTransactionFromCsvFile',param=each.file)"/>
+                                            </listcell>
+                                        </listitem>
+                                    </template>
+                                </listbox>
+                            </center>
+                        </borderlayout>
+                    </tabpanel>
+                    <tabpanel>
+                        <borderlayout vflex="1">
+                            <north>
+                                <hbox align="center" pack="center">
+                                    <label hflex="1" value="Schema"/>
+                                    <combobox hflex="2" model="@load(vm.schemas)" selectedItem="@bind(vm.realTransactionSchema)"
+                                              onChange="@command('changeRealTransactionSchema')" placeholder="choose schema"
+                                              style="text-align:center">
+                                        <template name="model">
+                                            <comboitem label="@load(each)"/>
+                                        </template>
+                                    </combobox>
+                                    <label hflex="1" value="Table" style="text-align: center"/>
+                                    <combobox hflex="2" model="@load(vm.realTransactionTables)" selectedItem="@bind(vm.realTransactiontable)"
+                                              onChange="@command('changeRealTransactionTable')" placeholder="choose table"
+                                              style="text-align:center">
+                                        <template name="model">
+                                            <comboitem label="@load(each)"/>
+                                        </template>
+                                    </combobox>
+                                    <button disabled="@load(empty vm.realTransactiontable)" iconSclass="z-icon-upload" tooltiptext="Upload database content" onClick="@command('uploadRealTransactionFromDatabase')"/>
+                                </hbox>
+                            </north>
+                            <center autoscroll="true">
+                                <grid model="@load(vm.fieldProperties)" emptyMessage="Please select a database" style="text-align:center">
+                                    <columns>
+                                        <column hflex="1" label="Field name" align="center"/>
+                                        <column hflex="1" label="Field type" align="center"/>
+                                    </columns>
+                                    <template name="model">
+                                        <row>
+                                            <label value="@load(each.fieldName)"/>
+                                            <label value="@load(each.fieldType)"/>
+                                        </row>
+                                    </template>
+                                </grid>
+                            </center>
+                        </borderlayout>
+                    </tabpanel>
+                </tabpanels>
+            </tabbox>
+        </center>
+    </borderlayout>
+</zk>
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/web/zul/start.zul b/CoreManager/src/main/resources/web/zul/start.zul
new file mode 100644 (file)
index 0000000..150219b
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
+<?page title="Core Manager"?>
+<zk>
+<!--    <style src="~./css/style.css"/>-->
+    <window border="normal" height="100%" width="100%">
+        <caption>
+            <label value="Core Manager"/>
+        </caption>
+        <tabbox vflex="1">
+            <tabs>
+                <tab label="Estimator Registry"/>
+                <tab label="System Qualifying"/>
+            </tabs>
+            <tabpanels>
+                <tabpanel style="overflow: auto">
+                    <include src="~./zul/estimatorRegistry.zul"/>
+                </tabpanel>
+                <tabpanel>
+                    <include src="~./zul/qualifying.zul"/>
+                </tabpanel>
+            </tabpanels>
+        </tabbox>
+    </window>
+</zk>
\ No newline at end of file
diff --git a/CoreManager/src/main/resources/wsdl/detection.wsdl b/CoreManager/src/main/resources/wsdl/detection.wsdl
new file mode 100644 (file)
index 0000000..288eca3
--- /dev/null
@@ -0,0 +1,140 @@
+<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://spring.io/guides/gs-producing-web-service" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://spring.io/guides/gs-producing-web-service" targetNamespace="http://spring.io/guides/gs-producing-web-service">
+    <wsdl:types>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://spring.io/guides/gs-producing-web-service">
+            <xs:element name="generateDetectionRequest">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="1" minOccurs="1" name="transaction_id" type="xs:string"/>
+                        <xs:element maxOccurs="1" minOccurs="1" name="detection_request" type="tns:detection_request"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="generateGenericDetectionRequest">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="unbounded" minOccurs="1" name="detection_request" type="tns:generic_detection_request"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="storeRealTransactionRequest">
+                <xs:complexType>
+                    <xs:sequence maxOccurs="unbounded" minOccurs="1">
+                        <xs:element maxOccurs="unbounded" minOccurs="1" name="store_request" type="tns:store_request"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="storeRealTransactionResponse">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="1" minOccurs="1" name="store_result" type="tns:store_result"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="generateDetectionResponse">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element maxOccurs="1" minOccurs="1" name="detection_response" type="tns:detection_response"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+            <xs:complexType name="detection_request">
+                <xs:sequence>
+                    <xs:element maxOccurs="1" minOccurs="1" name="transaction_id" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="card_number" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="transaction_type" type="xs:int"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="timestamp" type="xs:dateTime"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="amount" type="xs:int"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="currency_name" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="response_code" type="xs:int"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="country_name" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="vendor_code" type="xs:string"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="generic_detection_request">
+                <xs:sequence>
+                    <xs:element maxOccurs="1" minOccurs="1" name="field_name" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="value" type="xs:string"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="detection_response">
+                <xs:sequence>
+                    <xs:element name="prediction" type="xs:int"/>
+                    <xs:element name="negative_probability" type="xs:double"/>
+                    <xs:element name="positive_probability" type="xs:double"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="store_request">
+                <xs:sequence>
+                    <xs:element maxOccurs="1" minOccurs="1" name="transaction_id" type="xs:string"/>
+                    <xs:element maxOccurs="1" minOccurs="1" name="fraud" type="xs:int"/>
+                </xs:sequence>
+            </xs:complexType>
+            <xs:complexType name="store_result">
+                <xs:sequence>
+                    <xs:element name="result" type="xs:string"/>
+                    <xs:element name="record_number" type="xs:int"/>
+                </xs:sequence>
+            </xs:complexType>
+        </xs:schema>
+    </wsdl:types>
+    <wsdl:message name="storeRealTransactionResponse">
+        <wsdl:part element="tns:storeRealTransactionResponse" name="storeRealTransactionResponse"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="storeRealTransactionRequest">
+        <wsdl:part element="tns:storeRealTransactionRequest" name="storeRealTransactionRequest"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="generateDetectionRequest">
+        <wsdl:part element="tns:generateDetectionRequest" name="generateDetectionRequest"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="generateDetectionResponse">
+        <wsdl:part element="tns:generateDetectionResponse" name="generateDetectionResponse"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:message name="generateGenericDetectionRequest">
+        <wsdl:part element="tns:generateGenericDetectionRequest" name="generateGenericDetectionRequest"> </wsdl:part>
+    </wsdl:message>
+    <wsdl:portType name="DetectionPort">
+        <wsdl:operation name="storeRealTransaction">
+            <wsdl:input message="tns:storeRealTransactionRequest" name="storeRealTransactionRequest"> </wsdl:input>
+            <wsdl:output message="tns:storeRealTransactionResponse" name="storeRealTransactionResponse"> </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateDetection">
+            <wsdl:input message="tns:generateDetectionRequest" name="generateDetectionRequest"> </wsdl:input>
+            <wsdl:output message="tns:generateDetectionResponse" name="generateDetectionResponse"> </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateGenericDetection">
+            <wsdl:input message="tns:generateGenericDetectionRequest" name="generateGenericDetectionRequest"> </wsdl:input>
+        </wsdl:operation>
+    </wsdl:portType>
+    <wsdl:binding name="DetectionPortSoap11" type="tns:DetectionPort">
+        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
+        <wsdl:operation name="storeRealTransaction">
+            <soap:operation soapAction=""/>
+            <wsdl:input name="storeRealTransactionRequest">
+                <soap:body use="literal"/>
+            </wsdl:input>
+            <wsdl:output name="storeRealTransactionResponse">
+                <soap:body use="literal"/>
+            </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateDetection">
+            <soap:operation soapAction=""/>
+            <wsdl:input name="generateDetectionRequest">
+                <soap:body use="literal"/>
+            </wsdl:input>
+            <wsdl:output name="generateDetectionResponse">
+                <soap:body use="literal"/>
+            </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="generateGenericDetection">
+            <soap:operation soapAction=""/>
+            <wsdl:input name="generateGenericDetectionRequest">
+                <soap:body use="literal"/>
+            </wsdl:input>
+        </wsdl:operation>
+    </wsdl:binding>
+    <wsdl:service name="DetectionPortService">
+        <wsdl:port binding="tns:DetectionPortSoap11" name="DetectionPortSoap11">
+            <soap:address location="http://localhost:8081/ws"/>
+        </wsdl:port>
+    </wsdl:service>
+</wsdl:definitions>
\ No newline at end of file
diff --git a/CoreManager/src/test/java/hu/user/coremanager/CoreManagerApplicationTests.java b/CoreManager/src/test/java/hu/user/coremanager/CoreManagerApplicationTests.java
new file mode 100644 (file)
index 0000000..6f1f43f
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.coremanager;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CoreManagerApplicationTests {
+
+       @Test
+       void contextLoads() {
+       }
+
+}
diff --git a/Estimator/Dockerfile b/Estimator/Dockerfile
new file mode 100644 (file)
index 0000000..a33b216
--- /dev/null
@@ -0,0 +1,17 @@
+# set base image (host OS)
+FROM python:3.8
+
+# set the working directory in the container
+WORKDIR /code
+
+# copy the dependencies file to the working directory
+COPY requirements.txt .
+
+# install dependencies
+RUN pip install -r requirements.txt
+
+# copy the content of the local src directory to the working directory
+COPY src/ .
+
+# command to run on container start
+CMD [ "python", "./server.py" ]
\ No newline at end of file
diff --git a/Estimator/requirements.txt b/Estimator/requirements.txt
new file mode 100644 (file)
index 0000000..9253def
--- /dev/null
@@ -0,0 +1,23 @@
+click==8.1.3
+Flask==2.0.3
+importlib-metadata==4.12.0
+itsdangerous==2.1.2
+Jinja2==3.1.2
+joblib==1.2.0
+lightgbm==3.3.3
+MarkupSafe==2.1.1
+mysql-connector==2.2.9
+mysql-connector-python==8.0.28
+numpy==1.21.6
+pandas==1.3.5
+pika==1.2.1
+python-dateutil==2.8.2
+pytz==2022.2.1
+scikit-learn==1.0.2
+scipy==1.7.3
+six==1.16.0
+threadpoolctl==3.1.0
+typing_extensions==4.3.0
+Werkzeug==2.2.2
+xgboost==1.6.2
+zipp==3.8.1
diff --git a/Estimator/src/__pycache__/database_module.cpython-37.pyc b/Estimator/src/__pycache__/database_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..f84fdfb
Binary files /dev/null and b/Estimator/src/__pycache__/database_module.cpython-37.pyc differ
diff --git a/Estimator/src/__pycache__/engineering_module.cpython-37.pyc b/Estimator/src/__pycache__/engineering_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..1797a79
Binary files /dev/null and b/Estimator/src/__pycache__/engineering_module.cpython-37.pyc differ
diff --git a/Estimator/src/database_module.py b/Estimator/src/database_module.py
new file mode 100644 (file)
index 0000000..fe9989d
--- /dev/null
@@ -0,0 +1,197 @@
+import json
+import logging
+import mysql.connector
+from mysql.connector import pooling
+import numpy as np
+import pickle
+import sklearn
+import lightgbm
+
+
+class NumpyMySQLConverter(mysql.connector.conversion.MySQLConverter):
+    """ A mysql.connector Converter that handles Numpy types """
+
+    def _float32_to_mysql(self, value):
+        return float(value)
+
+    def _float64_to_mysql(self, value):
+        return float(value)
+
+    def _int32_to_mysql(self, value):
+        return int(value)
+
+    def _int64_to_mysql(self, value):
+        return int(value)
+
+
+class Handler:
+    def __init__(self, database_url, database_user, database_password):
+        self.logger = logging.getLogger("estimator.server.database")
+        self.database_url = database_url
+        self.database_user = database_user
+        self.database_password = database_password
+        # self.connection = self.get_connection(self.database_url, self.database_user, self.database_password)
+        self.connection_pool = self.get_connection_pool(self.database_url, self.database_user, self.database_password)
+
+    def get_connection_pool(self, database_url, database_user_name, database_password):
+        try:
+            connection_pool = pooling.MySQLConnectionPool(pool_name="estimator_pool",
+                                                          pool_size=8,
+                                                          pool_reset_session=True,
+                                                          host=database_url,
+                                                          user=database_user_name,
+                                                          password=database_password)
+            self.logger.debug("database connection pool created in train modul")
+            return connection_pool
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL connection error: {err.msg}")
+
+    def get_connection_from_pool(self):
+        try:
+            connection=self.connection_pool.get_connection()
+            connection.set_converter_class(NumpyMySQLConverter)
+            return connection
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    # def get_connection(self, database_url, database_user_name, database_password):
+    #     try:
+    #         connection = mysql.connector.connect(
+    #             # pool_name="local",
+    #             # pool_size=16,
+    #             host=database_url,
+    #             user=database_user_name,
+    #             password=database_password)
+    #         self.logger.debug("database connection created in estimator modul")
+    #         connection.set_converter_class(NumpyMySQLConverter)
+    #         return connection
+    #     except mysql.connector.Error as err:
+    #         self.logger.error(f"MySQL connection error: {err.msg}")
+
+    def get_schemas(self):
+        connection_object = self.get_connection_from_pool()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SHOW SCHEMAS"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            result = self.build_result_from_query_result(query_result)
+            return result if query_result is not None else None
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_tables_of_common_fraud(self, schema_name):
+        connection_object = self.get_connection_from_pool()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT distinct TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schema_name}'"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            result = self.build_result_from_query_result(query_result)
+            return result if query_result is not None else None
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def build_result_from_query_result(self, query_result):
+        result = list()
+        for item in query_result:
+            temp = item[0]
+            result.append(temp)
+        return result
+
+    def get_available_estimator_ids(self):
+        connection_object = self.get_connection_from_pool()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT id FROM common_fraud.estimator"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            response = list()
+            if query_result is not None:
+                for item in query_result:
+                    response.append(item[0])
+            return response if query_result is not None else None
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_estimator_properties(self, id):
+        connection_object = self.get_connection_from_pool()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT * FROM common_fraud.estimator WHERE id = %s"
+        try:
+            parameter = (id,)
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            train_task_id = query_result[1]
+            estimator_properties = pickle.loads(query_result[3])
+            pipeline = estimator_properties.get("pipeline")
+            label_encoder_registry = estimator_properties.get("label_encoder_registry")
+            encoding_parameters = estimator_properties.get("encoding_parameters")
+            feature_engineering_parameters = estimator_properties.get("feature_engineering_parameters")
+            encoded_field_names = estimator_properties.get("encoded_field_names")
+            time_base_field_name = estimator_properties.get("time_base_field_name")
+            schema_name = estimator_properties.get("schema_name")
+            feature_engineered_table_name = estimator_properties.get("feature_engineered_table_name")
+            detailed_information_about_table = estimator_properties.get("detailed_information_about_table")
+            feature_engineered_insert_script = estimator_properties.get("feature_engineered_insert_script")
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(f"estimator object loaded from database, estimator_id: {id}")
+            return train_task_id, pipeline, label_encoder_registry, encoding_parameters, feature_engineering_parameters, encoded_field_names, time_base_field_name, schema_name, feature_engineered_table_name, detailed_information_about_table, feature_engineered_insert_script if query_result is not None else None
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def load_label_encoder_object(self, id):
+        connection_object = self.get_connection_from_pool()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT * FROM common_fraud.label_encoder WHERE id = %s"
+        try:
+            parameter = (id,)
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            result = None
+            if query_result is not None:
+                result = pickle.loads(query_result[1])
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_feature_to_be_engineered_and_time_base_field_value_for_feature_engineering(self, schema_name,
+                                                                                       feature_engineered_table_name,
+                                                                                       feature_to_be_engineered,
+                                                                                       time_base_field_name,
+                                                                                       # time_of_input_data,
+                                                                                       retrospective_time,
+                                                                                       card_number_value):
+        connection_object = self.get_connection_from_pool()
+        cursor = connection_object.cursor()
+        float_retrespective_time = float(retrospective_time)
+        sql_select_query = f"SELECT {feature_to_be_engineered}, {time_base_field_name} FROM {schema_name}.{feature_engineered_table_name} WHERE card_number = %s and  {time_base_field_name} > %s ORDER BY {time_base_field_name} DESC"
+        try:
+            parameter = (card_number_value, float_retrespective_time)
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            return np.array(query_result) if query_result is not None else np.array([[0,0]])
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def persist_feature_engineered_record(self, insert_script, data):
+        connection_object = self.get_connection_from_pool()
+        cursor = connection_object.cursor()
+        try:
+            cursor.execute(insert_script, data)
+            connection_object.commit()
+            connection_object.close()
+            self.logger.debug("record persisted in feature engineered table")
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
diff --git a/Estimator/src/engineering_module.py b/Estimator/src/engineering_module.py
new file mode 100644 (file)
index 0000000..5b36be9
--- /dev/null
@@ -0,0 +1,130 @@
+import logging
+import math
+import statistics
+import multiprocessing as mp
+from multiprocessing import Process, Pool
+import numpy as np
+
+CARD_NUMBER = "card_number"
+UNDER_SCORE = "_"
+
+
+class Engineer:
+    def __init__(self, time_base_field_name, encoded_field_names, feature_engineering_parameters):
+        self.logger = logging.getLogger("estimator.server.database.engineer")
+        self.time_base_field_name = time_base_field_name
+        self.encoded_field_names = encoded_field_names
+        self.feature_engineering_parameters = feature_engineering_parameters
+        self.chosen_feature_field_name = self.feature_engineering_parameters.get("chosen_feature_field")
+        self.intervals = self.feature_engineering_parameters.get("intervals")
+
+    def process(self, encoded_input_data, previous_feature_and_time_base_array_by_intervals,
+                index_of_feature_to_be_engineered, index_of_time_base_field):
+        current_feature_value_to_be_engineered = encoded_input_data[index_of_feature_to_be_engineered]
+        current_time_stamp_value = encoded_input_data[index_of_time_base_field]
+        for interval, previous_feature_and_time_base_array in previous_feature_and_time_base_array_by_intervals.items():
+            previous_feature_array = previous_feature_and_time_base_array[:, :1]
+            previous_time_stamp_array = previous_feature_and_time_base_array[:, 1:]
+            previous_feature_list = previous_feature_array.flatten().tolist()
+            previous_time_stamp_list = previous_time_stamp_array.flatten().tolist()
+            combined_feature_list = list()
+            combined_time_stamp_list = list()
+            combined_feature_list.append(current_feature_value_to_be_engineered)
+            combined_feature_list.extend(previous_feature_list)
+            combined_time_stamp_list.append(current_time_stamp_value)
+            combined_time_stamp_list.extend(previous_time_stamp_list)
+            generated_features_based_on_chosen_feature = self.get_generated_features_based_on_chosen_feature(
+                current_feature_value_to_be_engineered, combined_feature_list)
+            generated_features_based_on_transaction_number = self.get_generated_features_based_on_transaction_number(
+                current_time_stamp_value, combined_time_stamp_list, interval)
+            generated_features_based_on_transaction_interval = self.get_generated_features_based_on_transaction_interval(
+                combined_time_stamp_list)
+            encoded_input_data.extend(generated_features_based_on_chosen_feature)
+            encoded_input_data.extend(generated_features_based_on_transaction_number)
+            encoded_input_data.extend(generated_features_based_on_transaction_interval)
+        return encoded_input_data
+
+    def get_generated_features_based_on_chosen_feature(self, current_feature_value,
+                                                       combined_feature_list):
+        average_feature_value = statistics.mean(combined_feature_list)
+        deviation_feature_value = statistics.stdev(combined_feature_list)
+        median_feature_value = statistics.median(combined_feature_list)
+        result = list()
+        feature_per_average_feature_value = current_feature_value / average_feature_value if average_feature_value != 0 else 0
+        result.append(feature_per_average_feature_value)
+        feature_minus_average_feature_value = current_feature_value - average_feature_value
+        result.append(feature_minus_average_feature_value)
+        feature_per_median_feature_value = current_feature_value / median_feature_value if median_feature_value != 0 else 0
+        result.append(feature_per_median_feature_value)
+        feature_minus_median_feature_value = current_feature_value - median_feature_value
+        result.append(feature_minus_median_feature_value)
+        feature_minus_average_feature_per_deviation_feature_value = (current_feature_value - average_feature_value) / deviation_feature_value if deviation_feature_value != 0 else 0
+        result.append(feature_minus_average_feature_per_deviation_feature_value)
+        feature_minus_average_feature_minus_deviation_feature_value = current_feature_value - average_feature_value - deviation_feature_value
+        result.append(feature_minus_average_feature_minus_deviation_feature_value)
+        return result
+
+    def get_generated_features_based_on_transaction_number(self, current_time_stamp_value, combined_time_stamp_list,
+                                                           interval):
+        transaction_list_on_last_day = list()
+        for time_stamp in combined_time_stamp_list:
+            if time_stamp > current_time_stamp_value - 1:
+                transaction_list_on_last_day.append(time_stamp)
+        transaction_number_on_last_day = len(transaction_list_on_last_day)
+        average_transaction_number = len(combined_time_stamp_list) / interval
+        median_transaction_number = self.get_median_transaction_number(combined_time_stamp_list)
+        result = list()
+        transaction_number_on_current_day_per_daily_average_transaction_number = transaction_number_on_last_day / average_transaction_number if average_transaction_number != 0 else 0
+        result.append(transaction_number_on_current_day_per_daily_average_transaction_number)
+        transaction_number_on_current_day_minus_daily_average_transaction_number = transaction_number_on_last_day - average_transaction_number
+        result.append(transaction_number_on_current_day_minus_daily_average_transaction_number)
+        transaction_number_on_current_day_per_daily_median_transaction_number = transaction_number_on_last_day / median_transaction_number if median_transaction_number != 0 else 0
+        result.append(transaction_number_on_current_day_per_daily_median_transaction_number)
+        transaction_number_on_current_day_minus_daily_median_transaction_number = transaction_number_on_last_day - median_transaction_number
+        result.append(transaction_number_on_current_day_minus_daily_median_transaction_number)
+        return result
+
+    def get_median_transaction_number(self,combined_time_stamp_list):
+        time_stamp_by_date_in_julian_format=dict()
+        for time_stamp_date_in_julian_format in combined_time_stamp_list:
+            current_day_in_julian_format=int(time_stamp_date_in_julian_format)
+            if time_stamp_by_date_in_julian_format.get(current_day_in_julian_format) is None:
+                time_stamps_on_given_day=list()
+                time_stamps_on_given_day.append(time_stamp_date_in_julian_format)
+                time_stamp_by_date_in_julian_format[current_day_in_julian_format]=time_stamps_on_given_day
+            else:
+                time_stamps_on_given_day=time_stamp_by_date_in_julian_format.get(current_day_in_julian_format)
+                time_stamps_on_given_day.append(time_stamp_date_in_julian_format)
+        transaction_numbers_on_days=list()
+        for time_stamps in time_stamp_by_date_in_julian_format.values():
+            transaction_numbers_on_days.append(len(time_stamps))
+        median_transsction_number=statistics.median(transaction_numbers_on_days)
+        return median_transsction_number
+
+    def get_generated_features_based_on_transaction_interval(self, combined_time_stamp_list):
+        number_of_time_stamps = len(combined_time_stamp_list)
+        if number_of_time_stamps>=2:
+            current_distance = combined_time_stamp_list[0] - combined_time_stamp_list[1]
+            distances = list()
+            for i in range(number_of_time_stamps - 1):
+                distance = combined_time_stamp_list[i] - combined_time_stamp_list[i + 1]
+                distances.append(distance)
+
+            average_distance_between_transactions = statistics.mean(distances)
+            median_distance_between_transactions = statistics.median(distances)
+            result = list()
+            average_distance_per_current_distance = average_distance_between_transactions / current_distance if current_distance != 0 else 0
+            average_distance_time_minus_current_distance_between_transactions = average_distance_between_transactions - current_distance
+            median_distance_per_current_distance_between_transactions = median_distance_between_transactions / current_distance if current_distance != 0 else 0
+            median_distance_minus_current_distance_between_transactions = median_distance_between_transactions - current_distance
+        else:
+            average_distance_per_current_distance = 0
+            average_distance_time_minus_current_distance_between_transactions = 0
+            median_distance_per_current_distance_between_transactions = 0
+            median_distance_minus_current_distance_between_transactions = 0
+
+        result.append(average_distance_per_current_distance)
+        result.append(average_distance_time_minus_current_distance_between_transactions)
+        result.append(median_distance_per_current_distance_between_transactions)
+        result.append(median_distance_minus_current_distance_between_transactions)
+        return result
diff --git a/Estimator/src/server.py b/Estimator/src/server.py
new file mode 100644 (file)
index 0000000..802362b
--- /dev/null
@@ -0,0 +1,423 @@
+import sys
+from datetime import datetime
+import dateutil.parser
+import time
+import logging
+import os
+
+import numpy as np
+import pandas as pd
+import pickle
+from xgboost import XGBClassifier
+from flask import Flask, jsonify, request, Response
+
+import database_module
+import engineering_module
+
+FRAUD_TYPE = "fraud_type"
+ID = "id"
+TYPE = "type"
+NAME = "name"
+INT = "int"
+FLOAT = "float"
+STRING = "str"
+DATETIME = "datetime"
+LABEL_ENCODER = "label_encoder"
+JULIAN = "julian"
+INTERVALS = "intervals"
+CARD_NUMBER = "card_number"
+CHOSEN_FEATURE_FIELD_FOR_FEATURE_ENGINEERING = "chosen_feature_field"
+COMMON_FRAUD_SCHEMA = "common_fraud"
+
+server = Flask(__name__)
+
+
+class InValidRequestParameterException(Exception):
+    pass
+
+
+class NotImplementedConversionTypeException(Exception):
+    pass
+
+
+class MissingInputFieldNameException(Exception):
+    pass
+
+
+@server.route('/prediction', methods=['POST'])
+def get_prediction():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    request_parameter = request.get_json()
+    try:
+        start_predict = time.time()
+        result = estimator_holder.predict_using_all_estimator(request_parameter)
+        finish_predict = time.time()
+        predict_processing_time = finish_predict - start_predict
+        logger.info(f"prediction finished, result: {result}, processing time: {predict_processing_time}")
+        return jsonify(result)
+    except MissingInputFieldNameException as e:
+        details = e.args[0]
+        message = details.get("message")
+        return Response(message, status=400)
+    except InValidRequestParameterException as e:
+        details = e.args[0]
+        message = details.get("message")
+        return Response(message, status=400)
+    except NotImplementedConversionTypeException as e:
+        details = e.args[0]
+        message = details.get("message")
+        return Response(message, status=400)
+
+@server.route('/estimator', methods=['POST'])
+def add_estimator():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    request_parameter = request.get_json()
+    id = request_parameter.get("estimator_id")
+    registered_estimator_ids = estimator_holder.get_all_registered_estimator_id()
+    if id in registered_estimator_ids:
+        message = f"estimator with this id is already registered, id: {id}"
+        logger.error(message)
+        return Response(message, status=400)
+    try:
+        estimator_ids = estimator_holder.register_estimator(id)
+        logger.info(f"estimator (id: {id}) registered")
+        return jsonify(estimator_ids)
+    except InValidRequestParameterException as e:
+        details = e.args[0]
+        message = details.get("message")
+        return Response(message, status=400)
+
+
+@server.route('/estimator', methods=['GET'])
+def get_estimator():
+    result = estimator_holder.get_all_registered_estimator_id()
+    return jsonify(result)
+
+
+@server.route('/estimator/<id>', methods=['DELETE'])
+def delete_estimator(id):
+    try:
+        estimator_ids = estimator_holder.delete_estimator(id)
+        logger.info(f"estimator (id: {id}) deleted")
+        return jsonify(estimator_ids)
+    except InValidRequestParameterException as e:
+        details = e.args[0]
+        message = details.get("message")
+        logger.error(message)
+        return Response(message, status=400)
+
+
+class EstimatorHolder:
+    def __init__(self):
+        self.estimator_by_id = dict()
+
+    def register_estimator(self, id):
+        available_estimator_ids = database_handler.get_available_estimator_ids()
+        if id not in available_estimator_ids:
+            message = f"estimator doesn't exist, estimator id: {id}"
+            logger.error(message)
+            raise InValidRequestParameterException({"message": message})
+        estimator = Estimator(id)
+        estimator.initialize()
+        self.estimator_by_id[id] = estimator
+        return self.get_all_registered_estimator_id()
+
+    def delete_estimator(self, id):
+        try:
+            self.estimator_by_id.pop(int(id))
+        except KeyError:
+            message = f"estimator isn't registered with this id, estimator id: {id}"
+            logger.error(message)
+            raise InValidRequestParameterException(({"message": message}))
+        return self.get_all_registered_estimator_id()
+
+    def get_all_registered_estimator_id(self):
+        result = list()
+        for item in self.estimator_by_id.keys():
+            result.append(item)
+        return result
+
+    def predict_using_all_estimator(self, raw_input_data):
+        prediction_by_id = dict()
+        for id, estimator in self.estimator_by_id.items():
+            prediction, positive_probability, negative_probability = estimator.data_preparation_and_predict(
+                raw_input_data)
+            prediction_by_id[id] = {
+                "prediction": prediction,
+                "positive_probability": positive_probability,
+                "negative_probability": negative_probability
+            }
+        return prediction_by_id
+
+
+class Estimator:
+    def __init__(self, id):
+        self.id = id
+        self.train_task_id = None
+        self.pipeline = None
+        self.label_encoder_by_field_name = dict()
+        self.encoding_parameters = dict()
+        self.feature_engineering_parameters = dict()
+        self.encoded_field_names = None
+        self.time_base_field_name = None
+        self.schema_name = None
+        self.feature_engineered_table_name = None
+        self.detailed_information_about_table = None
+        self.feature_engineered_insert_script = None
+
+    def initialize(self):
+        train_task_id, pipeline, label_encoder_registry, encoding_parameters, feature_engineering_parameters, encoded_field_names, time_base_field_name, schema_name, feature_engineered_table_name, detailed_information_about_table, feature_engineered_insert_script = database_handler.get_estimator_properties(
+            self.id)
+        self.train_task_id = train_task_id
+        self.pipeline = pipeline
+        for field_name, label_encoder_id in label_encoder_registry.items():
+            label_encoder = database_handler.load_label_encoder_object(label_encoder_id)
+            self.label_encoder_by_field_name[field_name] = label_encoder
+        self.encoding_parameters = encoding_parameters
+        self.feature_engineering_parameters = feature_engineering_parameters
+        self.encoded_field_names = self.remove_fraud_type_field_name(encoded_field_names)
+        self.time_base_field_name = time_base_field_name
+        self.schema_name = schema_name
+        self.feature_engineered_table_name = feature_engineered_table_name
+        self.detailed_information_about_table = detailed_information_about_table
+        self.feature_engineered_insert_script = feature_engineered_insert_script
+
+    def remove_fraud_type_field_name(self, field_names):
+        for field_name, encoding_type in self.encoding_parameters.items():
+            if encoding_type == FRAUD_TYPE:
+                field_names.remove(field_name)
+        return field_names
+
+    def data_preparation_and_predict(self, raw_input_data):
+        input_data = self.data_preprocess(raw_input_data)
+        data_set = self.encode(input_data)
+        if self.feature_engineering_parameters is not None:
+            data_set = self.feature_engineering(data_set)
+        prediction, positive_probability, negative_probability = self.predict(data_set)
+        return prediction, positive_probability, negative_probability
+
+    def data_preprocess(self, raw_input_data):
+        preprocessed_inpud_data = dict()
+        field_names_in_input_data = list(raw_input_data.keys())
+        required_input_field_names = self.get_required_input_field_names()
+        is_input_contains_all_required_field_names = self.check_if_input_contains_required_field_names(
+            required_input_field_names, field_names_in_input_data)
+        if not is_input_contains_all_required_field_names:
+            message = "There is a missing input field name in the request"
+            logger.error(message)
+            raise MissingInputFieldNameException(({"message": message}))
+        required_input_field_name_by_type = self.get_required_input_field_name_by_type()
+        for required_input_field_name, required_type_in_python in required_input_field_name_by_type.items():
+            current_field_value = raw_input_data[required_input_field_name]
+            preprocessed_inpud_data[required_input_field_name] = self.convert_to_own_type(required_input_field_name,
+                                                                                          current_field_value,
+                                                                                          required_type_in_python)
+        return preprocessed_inpud_data
+
+    def check_if_input_contains_required_field_names(self, required_field_names, input_field_names):
+        return all(i in input_field_names for i in required_field_names)
+
+    def get_required_input_field_names(self):
+        return list(self.get_required_input_field_name_by_type().keys())
+
+    def get_required_input_field_name_by_type(self):
+        field_properties = self.detailed_information_about_table.get("fields")
+        chosen_fraud_type_field_name = None
+        for field_name_in_encoding_parameters, planned_encoding_type in self.encoding_parameters.items():
+            if planned_encoding_type == FRAUD_TYPE:
+                chosen_fraud_type_field_name = field_name_in_encoding_parameters
+        field_names_without_id_and_fraud_type_by_field_type = dict()
+        for field_property in field_properties:
+            field_name = field_property.get(NAME)
+            field_type_in_database = field_property.get(TYPE)
+            if field_name != ID and field_name != chosen_fraud_type_field_name:
+                field_names_without_id_and_fraud_type_by_field_type[field_name] = self.convert_to_python_data_type(
+                    field_type_in_database)
+        return field_names_without_id_and_fraud_type_by_field_type
+
+    def convert_to_python_data_type(self, field_type_in_database):
+        database_python_data_type_mapping = {
+            "int": "int",
+            "float": "float",
+            "varchar": "str",
+            "datetime": "datetime"
+        }
+        data_type_in_python = database_python_data_type_mapping.get(field_type_in_database)
+        return data_type_in_python
+
+    def convert_to_own_type(self, current_field_name, current_field_value, required_type):
+        if required_type == INT:
+            try:
+                converted_value = int(current_field_value)
+            except ValueError as e:
+                message = f"can not convert raw input data to integer , field name: {current_field_name}, raw data: {current_field_value}"
+                logger.error(message)
+                raise InValidRequestParameterException({"message": message})
+        elif required_type == FLOAT:
+            try:
+                converted_value = float(current_field_value)
+            except ValueError as e:
+                message = f"can not convert raw input data to float, field name: {current_field_name}, raw data: {current_field_value}"
+                logger.error(message)
+                raise InValidRequestParameterException({"message": message})
+        elif required_type == STRING:
+            try:
+                converted_value = str(current_field_value)
+            except ValueError as e:
+                message = f"can not convert raw input data to string, field name: {current_field_name}, raw data: {current_field_value}"
+                logger.error(message)
+                raise InValidRequestParameterException({"message": message})
+        elif required_type == DATETIME:
+            converted_value = self.convert_rfc3339_compliant(current_field_value)
+            if converted_value is None:
+                converted_value = self.convert_iso8601_compliant(current_field_value)
+                if converted_value is None:
+                    message = f"can not convert raw input data to datetime, field name: {current_field_name}, raw data: {current_field_value}"
+                    logger.error(message)
+                    raise InValidRequestParameterException({"message": message})
+        else:
+            message = f"unknow conversion type in preprocessing step, type: {required_type}"
+            logger.error(message)
+            raise NotImplementedConversionTypeException({{"message": message}})
+        return converted_value
+
+    def convert_rfc3339_compliant(self, input):
+        converted_value = None
+        try:
+            converted_value = datetime.fromisoformat(input)
+        except ValueError:
+            logger.debug(f"Not RFC 3339 convertable timestamp, value{input}")
+        return converted_value
+
+    def convert_iso8601_compliant(self, input):
+        converted_value = None
+        try:
+            converted_value = dateutil.parser.isoparse(input)
+        except ValueError:
+            logger.debug(f"Not ISO 8601 convertable timestamp, value{input}")
+        return converted_value
+
+    def encode(self, input_data):
+        encoded_field_values = list()
+        for field_name in self.encoded_field_names:
+            current_data = input_data.get(field_name)
+            current_conversion_type = self.encoding_parameters.get(field_name)
+            encoded_value = self.encode_single_field_value(field_name, current_data, current_conversion_type)
+            encoded_field_values.append(encoded_value)
+        return encoded_field_values
+
+    def encode_single_field_value(self, current_field_name, current_data, current_conversion_type):
+        if current_conversion_type is None:
+            result = current_data
+        elif current_conversion_type == INT:
+            try:
+                result = int(current_data)
+            except ValueError as e:
+                message = f"Can not convert to integer in encoding step, field name: {current_field_name}, raw data: {current_data}"
+                logger.error(message)
+                raise InValidRequestParameterException({"message": message})
+        elif current_conversion_type == FLOAT:
+            try:
+                result = float(current_data)
+            except ValueError as e:
+                message = f"Can not convert to float in encoding step, field name: {current_field_name}, raw data: {current_data}"
+                logger.error(message)
+                raise InValidRequestParameterException({"message": message})
+        elif current_conversion_type == LABEL_ENCODER:
+            label_encoder = self.label_encoder_by_field_name.get(current_field_name)
+            try:
+                converted_value = label_encoder.transform([current_data])
+                result = converted_value[0]
+            except ValueError as e:
+                inner_array = label_encoder.classes_
+                modified_inner_array = np.append(inner_array, [current_data])
+                label_encoder_inner_case_number=len(modified_inner_array)
+                label_encoder.classes_ = modified_inner_array
+                message = f"New item has been added to labelencoder inner classes, field name: {current_field_name}, raw data: {current_data}, inner case number: {label_encoder_inner_case_number}"
+                logger.debug(message)
+                self.label_encoder_by_field_name[current_field_name]=label_encoder
+                converted_value = label_encoder.transform([current_data])
+                result = converted_value[0]
+            except Exception as e:
+                message = f"can not convert to int using label encoder in encoding step, field name: {current_field_name}, raw data: {current_data}"
+                logger.error(message)
+                raise InValidRequestParameterException({"message": message})
+        elif current_conversion_type == JULIAN:
+            try:
+                ts = pd.Timestamp(current_data)
+                result = ts.to_julian_date()
+            except Exception as e:
+                message = f"can not convert to Julian date in encoding step, field name: {current_field_name}, raw data: {current_data}"
+                logger.error(message)
+                raise InValidRequestParameterException({"message": message})
+        else:
+            message = "unknow conversion type in encoding step"
+            logger.error(message)
+            raise NotImplementedConversionTypeException({"message": message})
+        return result
+
+    def feature_engineering(self, encoded_input_data):
+        index_of_time_base_field = self.encoded_field_names.index(self.time_base_field_name)
+        time_of_input_data = encoded_input_data[index_of_time_base_field]
+        index_of_card_number = self.encoded_field_names.index(CARD_NUMBER)
+        card_number_value = encoded_input_data[index_of_card_number]
+        feature_to_be_engineered = self.feature_engineering_parameters.get(CHOSEN_FEATURE_FIELD_FOR_FEATURE_ENGINEERING)
+        index_of_feature_to_be_engineered = self.encoded_field_names.index(feature_to_be_engineered)
+        intervals = self.feature_engineering_parameters.get(INTERVALS)
+        previous_feature_and_time_base_array_by_intervals = dict()
+        for interval in intervals:
+            retrospective_time = time_of_input_data - interval
+            previous_feature_and_time_base_array = database_handler.get_feature_to_be_engineered_and_time_base_field_value_for_feature_engineering(
+                self.schema_name,
+                self.feature_engineered_table_name,
+                feature_to_be_engineered,
+                self.time_base_field_name,
+                # time_of_input_data,
+                retrospective_time,
+                card_number_value)
+            previous_feature_and_time_base_array_by_intervals[interval] = previous_feature_and_time_base_array
+        engineer = engineering_module.Engineer(self.time_base_field_name, self.encoded_field_names,
+                                               self.feature_engineering_parameters)
+        engineered_data_set = engineer.process(encoded_input_data, previous_feature_and_time_base_array_by_intervals,
+                                               index_of_feature_to_be_engineered, index_of_time_base_field)
+        database_handler.persist_feature_engineered_record(self.feature_engineered_insert_script, engineered_data_set)
+        return engineered_data_set
+
+    def predict(self, data_set):
+        try:
+            current_prediction = self.pipeline.predict([data_set])
+            current_probability = self.pipeline.predict_proba([data_set])
+            prediction = int(current_prediction[0])
+            positive_probability = float(current_probability[0][1])
+            negative_probability = float(current_probability[0][0])
+            return prediction, positive_probability, negative_probability
+        except Exception as e:
+            print(e)
+
+
+if __name__ == "__main__":
+    log_file = os.getenv("ESTIMATOR_LOG_FILE", "/home/tomi/log/estimator.log")
+    log_level = os.getenv("ESTIMATOR_LOG_LEVEL", "DEBUG")
+    # rabbitmq_url = os.getenv("RABBITMQ_URL", "localhost")
+    database_url = os.getenv("MYSQL_DATABASE_URL", "localhost")
+    database_user = os.getenv("MYSQL_USER", "root")
+    database_password = os.getenv("MYSQL_ROOT_PASSWORD", "pwd")
+    file_handler = logging.FileHandler(log_file)
+    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+    file_handler.setFormatter(formatter)
+    logger = logging.getLogger("estimator.server")
+    logger.setLevel(log_level)
+    logger.addHandler(file_handler)
+    logger.debug("MYSQL_DATABASE_URL: " + database_url)
+    logger.debug("MYSQL_USER :" + database_user)
+    logger.debug("MYSQL_PASSWORD: " + database_password)
+    database_handler = database_module.Handler(database_url, database_user, database_password)
+    estimator_holder = EstimatorHolder()
+    logger.debug("estimator modul started")
+    server.run(host='0.0.0.0', port=8083, debug=True)
diff --git a/Train/Dockerfile b/Train/Dockerfile
new file mode 100644 (file)
index 0000000..7b5f81c
--- /dev/null
@@ -0,0 +1,17 @@
+#set base image (host OS)\r
+FROM python:3.7.7\r
+\r
+# set the working directory in the container\r
+WORKDIR /code\r
+\r
+# copy the dependencies file to the working directory\r
+COPY requirements.txt .\r
+\r
+# install dependencies\r
+RUN pip install --upgrade pip && pip install -r requirements.txt\r
+\r
+# copy the content of the local src directory to the working directory\r
+COPY src/ .\r
+\r
+# command to run on container start\r
+CMD [ "python", "./server.py" ]
\ No newline at end of file
diff --git a/Train/requirements.txt b/Train/requirements.txt
new file mode 100644 (file)
index 0000000..a8bc5f4
--- /dev/null
@@ -0,0 +1,36 @@
+aniso8601==9.0.1
+attrs==22.1.0
+click==8.0.3
+colorama==0.4.4
+Flask==2.0.2
+flask-restplus==0.12.1
+imbalanced-learn==0.9.0
+imblearn==0.0
+importlib-metadata==4.10.1
+importlib-resources==5.10.0
+itsdangerous==2.0.1
+Jinja2==3.0.3
+joblib==1.1.0
+jsonschema==4.16.0
+lightgbm==3.3.2
+MarkupSafe==2.1.1
+mysql-connector==2.2.9
+mysql-connector-python==8.0.28
+numpy==1.21.5
+pandas==1.3.5
+pika==1.2.0
+pkgutil_resolve_name==1.3.10
+protobuf==3.19.3
+pyrsistent==0.19.1
+python-dateutil==2.8.2
+pytz==2021.3
+scikit-learn==1.0.2
+scipy==1.7.3
+six==1.16.0
+sklearn==0.0
+threadpoolctl==3.0.0
+timestamp==0.0.1
+typing_extensions==4.0.1
+Werkzeug==2.2.2
+xgboost==1.6.2
+zipp==3.7.0
diff --git a/Train/src/SQL CREATE SCHEMA common_fraud.txt b/Train/src/SQL CREATE SCHEMA common_fraud.txt
new file mode 100644 (file)
index 0000000..49d96a6
--- /dev/null
@@ -0,0 +1 @@
+CREATE SCHEMA IF NOT EXISTS common_fraud
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE encoded_table_registry.txt b/Train/src/SQL CREATE TABLE encoded_table_registry.txt
new file mode 100644 (file)
index 0000000..f855af1
--- /dev/null
@@ -0,0 +1,13 @@
+CREATE TABLE IF NOT EXISTS common_fraud.encoded_table_registry
+(
+    id                      BIGINT NOT NULL AUTO_INCREMENT,
+    raw_dataset_id          BIGINT NOT NULL,
+    encoding_id             BIGINT NOT NULL,
+    time_base_field_name    VARCHAR(255),
+    encoded_table_name      VARCHAR(255),
+    encoded_field_names     JSON,
+    label_encoder_registry  JSON,
+    FOREIGN KEY (raw_dataset_id)    REFERENCES raw_dataset(id),
+    FOREIGN KEY (encoding_id)   REFERENCES encoding(id),
+    PRIMARY KEY (id)
+) engine = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE encoding.txt b/Train/src/SQL CREATE TABLE encoding.txt
new file mode 100644 (file)
index 0000000..09d35be
--- /dev/null
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS common_fraud.encoding
+(
+    id                      BIGINT NOT NULL AUTO_INCREMENT,
+    encoding_parameters     JSON,
+    primary key (id)
+) ENGINE = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE estimator.txt b/Train/src/SQL CREATE TABLE estimator.txt
new file mode 100644 (file)
index 0000000..b7d81ed
--- /dev/null
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS common_fraud.estimator
+(
+    id                                                      BIGINT NOT NULL AUTO_INCREMENT,
+    train_task_id                                           BIGINT NOT NULL,
+    transaction_identifier_field_name                       VARCHAR(255),
+    estimator_object                                        MEDIUMBLOB,
+    PRIMARY KEY (id),
+    FOREIGN KEY (train_task_id) REFERENCES train_task(id)
+) engine = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE feature_engineered_table_registry.txt b/Train/src/SQL CREATE TABLE feature_engineered_table_registry.txt
new file mode 100644 (file)
index 0000000..bf9b5d4
--- /dev/null
@@ -0,0 +1,11 @@
+CREATE TABLE IF NOT EXISTS common_fraud.feature_engineered_table_registry
+(
+    id                                      BIGINT NOT NULL AUTO_INCREMENT,
+    encoded_table_registry_id               BIGINT NOT NULL,
+    feature_engineering_id                  BIGINT NOT NULL,
+    feature_engineered_table_name           VARCHAR(255),
+    feature_engineered_insert_script        VARCHAR(4096),
+    FOREIGN KEY (encoded_table_registry_id) REFERENCES encoded_table_registry(id),
+    FOREIGN KEY (feature_engineering_id)    REFERENCES feature_engineering(id),
+    PRIMARY KEY (id)
+) engine = InnoDB
diff --git a/Train/src/SQL CREATE TABLE feature_engineering.txt b/Train/src/SQL CREATE TABLE feature_engineering.txt
new file mode 100644 (file)
index 0000000..3169f79
--- /dev/null
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS common_fraud.feature_engineering
+(
+    id                              BIGINT NOT NULL AUTO_INCREMENT,
+    feature_engineering_parameters  JSON,
+    PRIMARY KEY (id)
+) engine = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE fraud.txt b/Train/src/SQL CREATE TABLE fraud.txt
new file mode 100644 (file)
index 0000000..8af35ed
--- /dev/null
@@ -0,0 +1,12 @@
+CREATE TABLE IF NOT EXISTS common_fraud.fraud
+(
+    id                                                      BIGINT NOT NULL AUTO_INCREMENT,
+       transaction_uuid                                        VARCHAR(255),
+       predicted                                               BIT(1),
+       observed                                                BIT(1),
+       predicted_value                                         INT,
+       observed_value                                          INT,
+       positive_probability                                    DOUBLE PRECISION,
+       negative_probability                                    DOUBLE PRECISION,
+    PRIMARY KEY (id)
+) engine = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE label_encoder.txt b/Train/src/SQL CREATE TABLE label_encoder.txt
new file mode 100644 (file)
index 0000000..4e75fe1
--- /dev/null
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS common_fraud.label_encoder
+(
+    id                      BIGINT NOT NULL AUTO_INCREMENT,
+    encoder_object          MEDIUMBLOB,
+    primary key (id)
+) ENGINE = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE metrics.txt b/Train/src/SQL CREATE TABLE metrics.txt
new file mode 100644 (file)
index 0000000..8db3b2d
--- /dev/null
@@ -0,0 +1,30 @@
+CREATE TABLE IF NOT EXISTS common_fraud.metrics
+(
+    id                      BIGINT NOT NULL AUTO_INCREMENT,
+    estimator_id            BIGINT NOT NULL,
+    TP                      DOUBLE PRECISION,
+    FP                      DOUBLE PRECISION,
+    TN                      DOUBLE PRECISION,
+    FN                      DOUBLE PRECISION,
+    sensitivity             DOUBLE PRECISION,
+    specificity             DOUBLE PRECISION,
+    accuracy                DOUBLE PRECISION,
+    balanced_accuracy       DOUBLE PRECISION,
+    prec                    DOUBLE PRECISION,
+    recall                  DOUBLE PRECISION,
+    PPV                     DOUBLE PRECISION,
+    NPV                     DOUBLE PRECISION,
+    FNR                     DOUBLE PRECISION,
+    FPR                     DOUBLE PRECISION,
+    FDR                     DOUBLE PRECISION,
+    F_OR                    DOUBLE PRECISION,
+    f1                      DOUBLE PRECISION,
+    f_05                    DOUBLE PRECISION,
+    f2                      DOUBLE PRECISION,
+    MCC                     DOUBLE PRECISION,
+    ROCAUC                  DOUBLE PRECISION,
+    Youdens_statistic       DOUBLE PRECISION,
+    PRIMARY KEY(id),
+    FOREIGN KEY (estimator_id) REFERENCES estimator(id)
+) engine = InnoDB
+
diff --git a/Train/src/SQL CREATE TABLE planned_encoding_and_feature_engineering.txt b/Train/src/SQL CREATE TABLE planned_encoding_and_feature_engineering.txt
new file mode 100644 (file)
index 0000000..934b60a
--- /dev/null
@@ -0,0 +1,11 @@
+CREATE TABLE IF NOT EXISTS common_fraud.planned_encoding_and_feature_engineering
+(
+    id                                  BIGINT NOT NULL AUTO_INCREMENT,
+    schema_name                         VARCHAR(255),
+    table_name                          VARCHAR(255),
+    detailed_information_about_table    JSON,
+    time_base_field_name                VARCHAR(255),
+    encoding_parameters                 JSON,
+    feature_engineering_parameters      JSON,
+    PRIMARY KEY (id)
+) engine = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE raw_dataset.txt b/Train/src/SQL CREATE TABLE raw_dataset.txt
new file mode 100644 (file)
index 0000000..802954f
--- /dev/null
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS common_fraud.raw_dataset
+(
+    id                      BIGINT NOT NULL AUTO_INCREMENT,
+    schema_name             VARCHAR(255),
+    table_name              VARCHAR(255),
+    primary key (id)
+) ENGINE = InnoDB
\ No newline at end of file
diff --git a/Train/src/SQL CREATE TABLE train_task.txt b/Train/src/SQL CREATE TABLE train_task.txt
new file mode 100644 (file)
index 0000000..1881ed1
--- /dev/null
@@ -0,0 +1,14 @@
+CREATE TABLE IF NOT EXISTS common_fraud.train_task
+(
+    id                                                      BIGINT NOT NULL AUTO_INCREMENT,
+    unique_field_name                                       VARCHAR(255),
+    train_task_name                                         VARCHAR(255),
+    planned_encoding_and_feature_engineering_id             BIGINT,
+    feature_selector_code                                   INT,
+    sampler_code                                            INT,
+    scaler_code                                             INT,
+    model_code                                              INT,
+    test_size                                               DOUBLE,
+    progress_status                                         VARCHAR(255),
+    PRIMARY KEY (id)
+) engine = InnoDB
\ No newline at end of file
diff --git a/Train/src/Start RabbitMQ b/Train/src/Start RabbitMQ
new file mode 100644 (file)
index 0000000..41b2ae9
--- /dev/null
@@ -0,0 +1 @@
+docker run -d --hostname my-rabbit --name train_queue -p 5672:5672 -p 15672:15672 rabbitmq:3-management
\ No newline at end of file
diff --git a/Train/src/__pycache__/database_module.cpython-37.pyc b/Train/src/__pycache__/database_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..2ce7931
Binary files /dev/null and b/Train/src/__pycache__/database_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/ddl_build_module.cpython-37.pyc b/Train/src/__pycache__/ddl_build_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..8440e49
Binary files /dev/null and b/Train/src/__pycache__/ddl_build_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/encoding_module.cpython-37.pyc b/Train/src/__pycache__/encoding_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..b31b733
Binary files /dev/null and b/Train/src/__pycache__/encoding_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/engineering_module.cpython-37.pyc b/Train/src/__pycache__/engineering_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..55cf372
Binary files /dev/null and b/Train/src/__pycache__/engineering_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/error_module.cpython-37.pyc b/Train/src/__pycache__/error_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..bca1fa2
Binary files /dev/null and b/Train/src/__pycache__/error_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/exception_module.cpython-37.pyc b/Train/src/__pycache__/exception_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..9d89a07
Binary files /dev/null and b/Train/src/__pycache__/exception_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/server.cpython-37.pyc b/Train/src/__pycache__/server.cpython-37.pyc
new file mode 100644 (file)
index 0000000..027a64b
Binary files /dev/null and b/Train/src/__pycache__/server.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/test_ddl_build_module.cpython-37.pyc b/Train/src/__pycache__/test_ddl_build_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..5f7e7a8
Binary files /dev/null and b/Train/src/__pycache__/test_ddl_build_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/test_encoding_module.cpython-37.pyc b/Train/src/__pycache__/test_encoding_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..bee222f
Binary files /dev/null and b/Train/src/__pycache__/test_encoding_module.cpython-37.pyc differ
diff --git a/Train/src/__pycache__/test_server_module.cpython-37.pyc b/Train/src/__pycache__/test_server_module.cpython-37.pyc
new file mode 100644 (file)
index 0000000..a675082
Binary files /dev/null and b/Train/src/__pycache__/test_server_module.cpython-37.pyc differ
diff --git a/Train/src/database_module.py b/Train/src/database_module.py
new file mode 100644 (file)
index 0000000..d114513
--- /dev/null
@@ -0,0 +1,1632 @@
+import json
+
+import sys
+import time
+# from datetime import datetime
+import datetime
+
+import mysql.connector
+from mysql.connector import pooling
+import numpy as np
+import logging
+import pickle
+import pandas as pd
+import csv
+import random
+import timestamp as timestamp
+
+import encoding_module
+import engineering_module
+import ddl_build_module
+
+UNDER_SCORE = "_"
+ENCODED = "encoded"
+FE = "engineered"  # feature engineered
+REDUCED = "reduced"
+FOR_EVAULATED = "for_evaulated"
+SIMULATED_REAL = "simulated_real"
+DOESNT_EXIST = "doesnt_exist"
+OTHER_APPLICABLE_ENCODING_TYPES = "other_applicable_encoding_types"
+# Train task status:
+STORED = "STORED"
+IN_PROGRESS = "IN_PROGRESS"
+
+
+class IllegalArgumentException(Exception):
+    pass
+
+
+class TrainTaskExistYetException(Exception):
+    pass
+
+
+class ForeignKeyConstraintViolationException(Exception):
+    pass
+
+
+class NoneException(Exception):
+    pass
+
+
+class NumpyMySQLConverter(mysql.connector.conversion.MySQLConverter):
+    """ A mysql.connector Converter that handles Numpy types """
+
+    def _float32_to_mysql(self, value):
+        return float(value)
+
+    def _float64_to_mysql(self, value):
+        return float(value)
+
+    def _int32_to_mysql(self, value):
+        return int(value)
+
+    def _int64_to_mysql(self, value):
+        return int(value)
+
+
+class Handler:
+    def __init__(self, database_url, database_user, database_password):
+        self.logger = logging.getLogger("train.server.database")
+        self.database_url = database_url
+        self.database_user = database_user
+        self.database_password = database_password
+        # self.connection = self.get_connection(self.database_url, self.database_user, self.database_password)
+        self.connection_pool = self.get_connection_pool(self.database_url, self.database_user, self.database_password)
+
+    def get_connection_pool(self, database_url, database_user_name, database_password):
+        try:
+            connection_pool = pooling.MySQLConnectionPool(pool_name="train_pool",
+                                                          pool_size=8,
+                                                          pool_reset_session=True,
+                                                          host=database_url,
+                                                          user=database_user_name,
+                                                          password=database_password)
+            self.logger.debug("database connection pool created in train modul")
+            return connection_pool
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL connection error: {err.msg}")
+
+    # def get_connection(self, database_url, database_user_name, database_password):
+    #     try:
+    #         connection = mysql.connector.connect(
+    #             # pool_name="local",
+    #             # pool_size=16,
+    #             host=database_url,
+    #             user=database_user_name,
+    #             password=database_password)
+    #         self.logger.debug("database connection created in train modul")
+    #         connection.set_converter_class(NumpyMySQLConverter)
+    #         return connection
+    #     except mysql.connector.Error as err:
+    #         self.logger.error(f"MySQL connection error: {err.msg}")
+
+    def create_common_fraud_schemas(self):
+
+        commands = ["SQL CREATE SCHEMA common_fraud.txt",
+                    "SQL CREATE TABLE planned_encoding_and_feature_engineering.txt",
+                    "SQL CREATE TABLE raw_dataset.txt",
+                    "SQL CREATE TABLE encoding.txt",
+                    "SQL CREATE TABLE feature_engineering.txt",
+                    "SQL CREATE TABLE encoded_table_registry.txt",
+                    "SQL CREATE TABLE feature_engineered_table_registry.txt",
+                    "SQL CREATE TABLE train_task.txt",
+                    "SQL CREATE TABLE label_encoder.txt",
+                    "SQL CREATE TABLE estimator.txt",
+                    "SQL CREATE TABLE metrics.txt"
+                    ]
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        for command in commands:
+            try:
+                file = open(command, "r")
+                sql_create_script = file.read()
+                cursor.execute(sql_create_script)
+                connection_object.commit()
+            except FileNotFoundError:
+                self.logger.error(f"SQL script file not found {command}")
+            except OSError:
+                self.logger.error("OS Error")
+            except mysql.connector.Error as err:
+                self.logger.error(f"MySQL error message: {err.msg}, file: {file}")
+        cursor.close()
+        connection_object.close()
+
+    def upload_train_database(self, request_parameter):
+        schema_name = request_parameter.get("schema_name")
+        if schema_name is None:
+            raise IllegalArgumentException({"message": "Parameter is missing in request body",
+                                            "parameter_name": "schema_name"})
+        table_name = request_parameter.get("table_name")
+        if table_name is None:
+            raise IllegalArgumentException({"message": "Parameter is missing in request body",
+                                            "parameter_name": "table_name"})
+        field_type_by_name = request_parameter.get("fields")
+        if field_type_by_name is None:
+            raise IllegalArgumentException({"message": "Parameter is missing in request body",
+                                            "parameter_name": "fields"})
+        if type(field_type_by_name) is not dict:
+            raise IllegalArgumentException({"message": "The parameter isn't dictionary",
+                                            "parameter_name": "fields"})
+        path = request_parameter.get("path")
+        if path is None:
+            raise IllegalArgumentException({"message": "Parameter is missing in request body",
+                                            "parameter_name": "path"})
+        csv_file_name = request_parameter.get("csv_file_name")
+        if csv_file_name is None:
+            raise IllegalArgumentException({"message": "Parameter is missing in request body",
+                                            "parameter_name": "csv_file_name"})
+        csv_filename_with_path = path + "/" + csv_file_name
+        try:
+            data = pd.read_csv(csv_filename_with_path, delimiter=';', na_filter=None)
+            df = pd.DataFrame(data)
+            train_as_array = df.to_numpy()
+            train_to_persist = train_as_array[1:, ]
+        except FileNotFoundError:
+            raise IllegalArgumentException({"message": "CSV file not found"})
+        except OSError:
+            raise IllegalArgumentException({"message": "OS error occured"})
+        self.create_train_schema(schema_name)
+        self.create_train_table(schema_name, table_name, field_type_by_name, True)
+        ddl_command_builder = ddl_build_module.DdlCommandBuilder()
+        insert_script = ddl_command_builder.create_insert_into_generic_train_database_script(schema_name, table_name,
+                                                                                             field_type_by_name)
+        self.persist_encoded_or_engineered_dataset(insert_script, train_to_persist, "train")
+
+    def create_train_schema(self, schema_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_command = f"CREATE SCHEMA IF NOT EXISTS {schema_name}"
+        try:
+            cursor.execute(sql_insert_command)
+            connection_object.commit()
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(f"Train schema created, schema name: {schema_name}")
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def create_train_table(self, schema_name, table_name, field_type_by_name, is_id_auto_incremented):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        ddl_command_builder = ddl_build_module.DdlCommandBuilder()
+        sql_insert_command = ddl_command_builder.build_create_generic_train_database_script(schema_name,
+                                                                                            table_name,
+                                                                                            field_type_by_name,
+                                                                                            is_id_auto_incremented)
+        try:
+            cursor.execute(sql_insert_command)
+            connection_object.commit()
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(f"Train table created, schema name: {schema_name}, table name: {table_name}")
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_schema_names(self):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SHOW SCHEMAS"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            schema_names = list()
+            for item in query_result:
+                schema_names.append(item[0])
+            self.logger.debug(f"schema names: {schema_names}")
+            return schema_names
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_table_names_of_given_database(self, schema_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT distinct TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schema_name}'"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            table_names = list()
+            for item in query_result:
+                table_names.append(item[0])
+            self.logger.debug(f"tables names of {schema_name} schema: {table_names}")
+            return table_names
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_records(self, database_name, table_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        result = dict()
+        sql_select_query = f"SELECT * FROM {database_name}.{table_name}"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            for record in query_result:
+                fields = list()
+                length = len(record)
+                id = 0
+                for index in range(length):
+                    if index == 0:
+                        id = record[index]
+                    else:
+                        fields.append(record[index])
+                result[id] = fields
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_records_with_limit_and_offset(self, database_name, table_name, limit, offset):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT * FROM {database_name}.{table_name}" + (
+            f" limit {limit}" if limit is not None else "") + (f" offset {offset}" if offset is not None else "")
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            records = list()
+            for item in query_result:
+                fields = list()
+                length = len(item)
+                for index in range(length):
+                    field = item[index]
+                    type_of_field = type(field)
+                    if type_of_field == datetime:
+                        field = datetime.strftime(field, '%Y-%m-%d %H:%M:%S:%f')
+                    fields.append(field)
+                records.append(fields)
+            return records
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_detailed_information_about_table(self, schema_name, table_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        result = dict()
+        column_properties = list()
+        if table_name is None:
+            self.logger.debug("No table name set, query the transaction table")
+            table_name = "transaction"
+        sql_select_query = f"SELECT COLUMN_NAME, DATA_TYPE  FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schema_name}' AND TABLE_NAME = '{table_name}' ORDER BY ordinal_position"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            if query_result:
+                for column_name_and_type in query_result:
+                    column_property = dict()
+                    column_name = column_name_and_type[0]
+                    column_type_as_byte = column_name_and_type[1]
+                    column_type = column_type_as_byte.decode("utf-8")
+                    column_property["name"] = column_name
+                    column_property["type"] = column_type
+                    column_properties.append(column_property)
+            result["fields"] = column_properties
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+        sql_select_query = f"SELECT COUNT(*) FROM {schema_name}.{table_name}"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchone()
+            result["record_number"] = query_result[0] if query_result else 0
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+        sql_select_query = f"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schema_name}' AND TABLE_NAME = '{table_name}' AND COLUMN_KEY='PRI'"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchone()
+            result["primary_key"] = query_result[0] if query_result else ""
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+        cursor.close()
+        connection_object.close()
+        fraud_candidates = self.get_fraud_candidate_column_and_fraud_number(schema_name, table_name,
+                                                                            result)
+        result["fraud_candidates"] = fraud_candidates
+        self.get_other_proper_column_data_type_to_encoding(schema_name, table_name, result)
+        return result
+
+    def get_fraud_candidate_column_and_fraud_number(self, schema_name, table_name, database_property_holder):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        candidate_fraud_columns = list()
+        column_names_and_types = database_property_holder.get("fields")
+        for column_name_and_type in column_names_and_types:
+            column_name = column_name_and_type.get("name")
+            sql_select_query = f"SELECT {column_name} FROM {schema_name}.{table_name}"
+            try:
+                cursor.execute(sql_select_query)
+                query_result = cursor.fetchall()
+                candidate_fraud_column = dict()
+                record_number = len(query_result)
+                number_of_ones = 0
+                number_of_nulls = 0
+                for field in query_result:
+                    if field[0] == 1 or field[0] == '1':
+                        number_of_ones = number_of_ones + 1
+                    elif field[0] == 0 or field[0] == '0':
+                        number_of_nulls = number_of_nulls + 1
+                if number_of_ones + number_of_nulls == record_number and number_of_ones != 0 and number_of_nulls != 0:
+                    candidate_fraud_column["name"] = column_name
+                    candidate_fraud_column["fraud_number"] = number_of_ones
+                    candidate_fraud_column["no_fraud_number"] = number_of_nulls
+                    candidate_fraud_columns.append(candidate_fraud_column)
+            except mysql.connector.Error as err:
+                self.logger.error(f"MySQL error message: {err.msg}")
+        cursor.close()
+        connection_object.close()
+        return candidate_fraud_columns
+
+    def get_other_proper_column_data_type_to_encoding(self, schema_name, table_name, database_property_holder):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        column_names_and_types = database_property_holder.get("fields")
+        for column_name_and_type in column_names_and_types:
+            column_name = column_name_and_type.get("name")
+            column_type = column_name_and_type.get("type")
+            other_applicable_encoding_types = list()
+            if column_type.startswith("varchar"):
+                other_applicable_encoding_types.clear()
+                sql_select_query = f"SELECT {column_name} FROM {schema_name}.{table_name}"
+                try:
+                    cursor.execute(sql_select_query)
+                    records = cursor.fetchall()
+                    is_records_number_type = True
+                    is_records_suited_int_type = True
+                    for record_as_tuple in records:
+                        record = record_as_tuple[0]
+                        if not record.isnumeric():
+                            is_records_number_type = False
+                            is_records_suited_int_type = False
+                            break
+                        elif self.is_int(record):
+                            value = int(record)
+                            if value > 2147483647:
+                                is_records_suited_int_type = False
+                            else:
+                                continue
+                        elif self.is_float(record):
+                            is_records_suited_int_type = False
+
+                    if is_records_number_type:
+                        other_applicable_encoding_types.append("float")
+                    if is_records_suited_int_type:
+                        other_applicable_encoding_types.append("int")
+                except mysql.connector.Error as err:
+                    self.logger.error(f"MySQL error message: {err.msg}")
+            column_name_and_type[OTHER_APPLICABLE_ENCODING_TYPES] = other_applicable_encoding_types
+        cursor.close()
+        connection_object.close()
+
+    def is_int(self, element):
+        try:
+            int(element)
+            return True
+        except ValueError:
+            return False
+
+    def is_float(self, element):
+        try:
+            float(element)
+            return True
+        except ValueError:
+            return False
+
+    def persist_encoding_and_engineering_plan(self, schema_name, table_name, detailed_information_about_table,
+                                              used_time_base_field_name,
+                                              encoding_parameters,
+                                              feature_engineering_parameters):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_query = "INSERT INTO common_fraud.planned_encoding_and_feature_engineering (schema_name, table_name, detailed_information_about_table, time_base_field_name ,encoding_parameters, feature_engineering_parameters) VALUES (%s,%s,%s,%s,%s,%s)"
+        parameter = (
+            schema_name, table_name, json.dumps(detailed_information_about_table), used_time_base_field_name,
+            json.dumps(encoding_parameters),
+            json.dumps(feature_engineering_parameters))
+        try:
+            cursor.execute(sql_insert_query, parameter)
+            last_inserted_row_id = cursor.lastrowid
+            connection_object.commit()
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(
+                f"encoding and feature enginnering plan persisted, schema name: {schema_name}, table name: {table_name}, encoding parameters: {encoding_parameters}, feature engineering parameters {feature_engineering_parameters}")
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+        return last_inserted_row_id
+
+    def get_all_encoding_and_feature_engineering_plan(self):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        response = dict()
+        sql_select_query = "SELECT * FROM common_fraud.planned_encoding_and_feature_engineering"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+        response = None
+        if query_result is not None:
+            response = self.build_encoding_and_feature_engineering_plan_response(query_result)
+        return response
+
+    # 2.1
+    def get_encoding_and_feature_engineering_plan_by_id(self, id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.planned_encoding_and_feature_engineering WHERE id = %s"
+        parameter = (id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+        if query_result is None:
+            raise NoneException({"message": "The given id doesn't exist", "parameter": id})
+        response = self.build_encoding_and_feature_engineering_plan_response(query_result)
+        return response
+
+    def delete_encoding_and_feature_engineering_plan(self, id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_delete_command = "DELETE FROM common_fraud.planned_encoding_and_feature_engineering WHERE id= %s"
+        parameter = (id,)
+        try:
+            cursor.execute(sql_delete_command, parameter)
+            connection_object.commit()
+            cursor.close()
+            connection_object.close()
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def build_encoding_and_feature_engineering_plan_response(self, query_result):
+        response = list()
+        for record in query_result:
+            single_response = dict()
+            single_response["id"] = record[0]
+            single_response["schema_name"] = record[1]
+            single_response["table_name"] = record[2]
+            single_response["detailed_information_about_table"] = json.loads(record[3])
+            single_response["time_base_field_name"] = record[4]
+            single_response["encoding_parameters"] = json.loads(record[5])
+            single_response["feature_engineering_parameters"] = json.loads(record[6])
+            response.append(single_response)
+        return response
+
+    def persist_train_task(self, request_parameter, feature_selector_keys, samplers_keys, scalers_keys, models_keys):
+        unique_field_name = request_parameter.get("unique_field_name")
+        if unique_field_name is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body",
+                 "parameter_name": "transaction_identifier_field_name"})
+        name = request_parameter.get("train_task_name")
+        if name is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body",
+                 "parameter_name": {name}})
+        planned_encoding_and_feature_engineering_id = request_parameter.get(
+            "planned_encoding_and_feature_engineering_id")
+        if planned_encoding_and_feature_engineering_id is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body",
+                 "parameter_name": "planned_encoding_and_feature_engineering_id"})
+        existing_planned_encoding_id_and_feature_engineering_id = self.get_existing_planned_encoding_and_feature_engineering_id()
+        if planned_encoding_and_feature_engineering_id not in existing_planned_encoding_id_and_feature_engineering_id:
+            raise IllegalArgumentException(
+                {"message": "Parameter is invalid", "parameter_name": "planned_encoding_and_feature_engineering_id",
+                 "value": planned_encoding_and_feature_engineering_id})
+
+        feature_selector_code = request_parameter.get("feature_selector_code")
+        if feature_selector_code is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body", "parameter_name": "feature selector code"})
+        if feature_selector_code not in feature_selector_keys:
+            raise IllegalArgumentException(
+                {"message": "Parameter is invalid", "parameter_name": "feature_selector_code",
+                 "value": feature_selector_code})
+
+        sampler_code = request_parameter.get("sampler_code")
+        if sampler_code is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body", "parameter_name": "sampler_code"})
+        if sampler_code not in samplers_keys:
+            raise IllegalArgumentException(
+                {"message": "Parameter is invalid", "parameter_name": "sampler_code",
+                 "value": sampler_code})
+
+        scaler_code = request_parameter.get("scaler_code")
+        if scaler_code is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body", "parameter_name": "scaler_code"})
+        if scaler_code not in scalers_keys:
+            raise IllegalArgumentException(
+                {"message": "Parameter is invalid", "parameter_name": "scaler_code",
+                 "value": scaler_code})
+
+        model_code = request_parameter.get("model_code")
+        if model_code is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body", "parameter_name": "model_code"})
+        if model_code not in models_keys:
+            raise IllegalArgumentException(
+                {"message": "Parameter is invalid", "parameter_name": "model_code",
+                 "value": model_code})
+
+        test_size = request_parameter.get("test_size")
+        if test_size is None:
+            raise IllegalArgumentException(
+                {"message": "Parameter is missing in request body", "parameter_name": "test_size"})
+        if test_size >= 1 or test_size <= 0:
+            raise IllegalArgumentException(
+                {"message": "Parameter must be between 0 and 1", "parameter_name": "test_size", "value": test_size})
+
+        progress_status = STORED
+
+        self.check_if_train_task_exist(name, planned_encoding_and_feature_engineering_id, feature_selector_code,
+                                       sampler_code, scaler_code,
+                                       model_code, test_size)
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_query = "INSERT INTO common_fraud.train_task (unique_field_name, train_task_name, planned_encoding_and_feature_engineering_id, feature_selector_code, sampler_code, scaler_code, model_code, test_size, progress_status) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)"
+        parameter = (
+            unique_field_name, name, planned_encoding_and_feature_engineering_id, feature_selector_code,
+            sampler_code, scaler_code, model_code, test_size, progress_status)
+        try:
+            cursor.execute(sql_insert_query, parameter)
+            connection_object.commit()
+            last_inserted_row_id = cursor.lastrowid
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(
+                f"Train task persisted, train_task_name: {name},planned_encoding_id_and_feature_engineering_id: {planned_encoding_and_feature_engineering_id}, feature_selector_code: {feature_selector_code}, sampler_code: {sampler_code}, scaler_code: {scaler_code}, model_code: {model_code}, test_size: {test_size}")
+            return last_inserted_row_id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def check_if_train_task_exist(self, name, planned_encoding_and_feature_engineering_id, feature_selector_code,
+                                  sampler_code, scaler_code, model_code, test_size):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.train_task WHERE train_task_name = %s and planned_encoding_and_feature_engineering_id = %s and feature_selector_code = %s and sampler_code = %s and scaler_code = %s and model_code = %s and test_size = %s"
+        parameter = (
+            name, planned_encoding_and_feature_engineering_id, feature_selector_code, sampler_code, scaler_code,
+            model_code, test_size)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            if query_result is not None:
+                id = query_result[0]
+                raise TrainTaskExistYetException({"message": "Train task exists yet", "id": id})
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_all_train_task(self):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.train_task"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            result = self.build_train_task_response(query_result)
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_train_task_by_id(self, id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.train_task WHERE id = %s"
+        parameter = (id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            result = self.build_train_task_response(query_result)
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def build_train_task_response(self, query_result):
+        result = dict()
+        for item in query_result:
+            id = item[0]
+            unique_field_name = item[1]
+            name = item[2]
+            planned_encoding_and_feature_engineering_id = item[3]
+            feature_selector_code = item[4]
+            sampler_code = item[5]
+            scaler_code = item[6]
+            model_code = item[7]
+            test_size = item[8]
+            process_status = item[9]
+            result[id] = {
+                "unique_field_name": unique_field_name,
+                "train_task_name": name,
+                "planned_encoding_and_feature_engineering_id": planned_encoding_and_feature_engineering_id,
+                "feature_selector_code": feature_selector_code,
+                "sampler_code": sampler_code,
+                "scaler_code": scaler_code,
+                "model_code": model_code,
+                "test_size": test_size,
+                "process_status": process_status}
+        return result
+
+    def set_train_task_status(self, id, progress_status):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_update_command = "UPDATE common_fraud.train_task SET progress_status = %s WHERE id = %s"
+        parameter = (progress_status, id)
+        try:
+            cursor.execute(sql_update_command, parameter)
+            connection_object.commit()
+            cursor.close()
+            connection_object.close()
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_train_task_status(self, id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT progress_status FROM common_fraud.train_task WHERE id = %s"
+        parameter = (id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            if query_result is not None:
+                status = query_result[0]
+            else:
+                status = DOESNT_EXIST
+            return status
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def create_train_task_csv_file(self, path, file_name, date_time_to_append, feature_selectors, samplers, scalers,
+                                   models):
+        header_elements, lines = self.create_train_task_csv_content(feature_selectors, samplers, scalers, models)
+        self.write_csv_file(path, file_name, date_time_to_append, header_elements, lines)
+
+    def create_train_task_csv_content(self, feature_selectors, samplers, scalers, models):
+        header_elements = (
+            "id", "transaction_identifier_field_name", "train_task_name", "planned_encoding_and_feature_engineering",
+            "feature_selector", "sampler", "scaler", "model", "test_size", "process_status")
+        lines = list()
+        train_task_properties_by_id = self.get_all_train_task()
+        for key, train_task_properties in train_task_properties_by_id.items():
+            line = list()
+            id = key
+            transaction_identifier_field_name = train_task_properties.get("transaction_identifier_field_name")
+            train_task_name = train_task_properties.get("train_task_name")
+            planned_encoding_and_feature_engineering_id = train_task_properties.get(
+                "planned_encoding_and_feature_engineering_id")
+            feature_selector_code = train_task_properties.get("feature_selector_code")
+            sampler_code = train_task_properties.get("sampler_code")
+            scaler_code = train_task_properties.get("scaler_code")
+            model_code = train_task_properties.get("model_code")
+            test_size = train_task_properties.get("test_size")
+            progress_status = train_task_properties.get("process_status")
+            planned_encoding_and_feature_engineering_dict = self.get_encoding_and_feature_engineering_plan_by_id(
+                planned_encoding_and_feature_engineering_id)
+            planned_encoding_and_feature_engineering_string = self.convert_encoding_and_feature_enginnering_dict_to_string(
+                planned_encoding_and_feature_engineering_dict[0])
+            line.append(id)
+            line.append(transaction_identifier_field_name)
+            line.append(train_task_name)
+            line.append(planned_encoding_and_feature_engineering_string)
+            line.append(feature_selectors.get(feature_selector_code)[0])
+            line.append(samplers.get(sampler_code)[0])
+            line.append(scalers.get(scaler_code)[0])
+            line.append(models.get(model_code)[0])
+            line.append(test_size)
+            line.append(progress_status)
+            lines.append(line)
+        return header_elements, lines
+
+    def create_metrics_csv_file(self, path, file_name, date_time_to_append):
+        header_elements, lines = self.create_metrics_csv_content()
+        self.write_csv_file(path, file_name, date_time_to_append, header_elements, lines)
+
+    def create_metrics_csv_content(self):
+        header_elements = (
+            "id", "train_task_name", "TP", "FP", "TN", "FN", "accuracy", "balanced_accuracy", "precision", "recall",
+            "sensitivity", "specificity", "PPV", "NPV", "FNR", "FPR", "FDR", "FOR", "f1", "f0.5", "f2", "MCC", "ROCAUC",
+            "Youdens_statistic")
+        lines = list()
+        list_of_metrics_dict = self.get_all_metrics()
+        for single_metrics_dict in list_of_metrics_dict:
+            line = list()
+            id = single_metrics_dict.get("id")
+            estimator_id = single_metrics_dict.get("estimator_id")
+            TP = single_metrics_dict.get("TP")
+            FP = single_metrics_dict.get("FP")
+            TN = single_metrics_dict.get("TN")
+            FN = single_metrics_dict.get("FN")
+            accuracy = single_metrics_dict.get("accuracy")
+            balanced_accuracy = single_metrics_dict.get("balanced_accuracy")
+            precision = single_metrics_dict.get("precision")
+            recall = single_metrics_dict.get("recall")
+            sensitivity = single_metrics_dict.get("sensitivity")
+            specificity = single_metrics_dict.get("specificity")
+            PPV = single_metrics_dict.get("PPV")
+            NPV = single_metrics_dict.get("NPV")
+            FNR = single_metrics_dict.get("FNR")
+            FPR = single_metrics_dict.get("FPR")
+            FDR = single_metrics_dict.get("FDR")
+            FOR = single_metrics_dict.get("FOR")
+            f1 = single_metrics_dict.get("f1")
+            f0_5 = single_metrics_dict.get("f0.5")
+            f2 = single_metrics_dict.get("f2")
+            MCC = single_metrics_dict.get("MCC")
+            ROCAUC = single_metrics_dict.get("ROCAUC")
+            Youdens_statistic = single_metrics_dict.get("Youdens_statistic")
+            train_task_name = self.get_train_task_name_by_estimator_id(estimator_id)
+            line.append(id)
+            line.append(train_task_name)
+            line.append(TP)
+            line.append(FP)
+            line.append(TN)
+            line.append(FN)
+            line.append(accuracy)
+            line.append(balanced_accuracy)
+            line.append(precision)
+            line.append(recall)
+            line.append(sensitivity)
+            line.append(specificity)
+            line.append(PPV)
+            line.append(NPV)
+            line.append(FNR)
+            line.append(FPR)
+            line.append(FDR)
+            line.append(FOR)
+            line.append(f1)
+            line.append(f0_5)
+            line.append(f2)
+            line.append(MCC)
+            line.append(ROCAUC)
+            line.append(Youdens_statistic)
+            lines.append(line)
+        return header_elements, lines
+
+    def convert_encoding_and_feature_enginnering_dict_to_string(self, input_dict):
+        keys = (
+            "schema_name", "table_name", "time_base_field_name", "encoding_parameters",
+            "feature_engineering_parameters")
+        line = str()
+        for key in keys:
+            line += key + " = "
+            value = input_dict.get(key)
+            if isinstance(value, list) or isinstance(value, dict):
+                flatted_value = str(value)
+                converted_flatted_value = flatted_value.replace(",", ";")
+                line += converted_flatted_value
+            else:
+                line += value if value is not None else "null"
+            if keys.index(key) != len(keys) - 1:
+                line += "; "
+        return line
+
+    def write_csv_file(self, path, file_name, date_time_to_append, header_elements, lines):
+        full_file_name = path + "/" + file_name + ".csv"
+        if date_time_to_append == True:
+            datetime_now = datetime.datetime.now()
+            year_month_day = datetime_now.strftime("%Y%m%d")
+            hour_min_sec = datetime_now.strftime("%H%M%S")
+            full_file_name = full_file_name + "_" + str(year_month_day) + "_" + str(hour_min_sec)
+            with open(full_file_name, 'w') as csv_file:
+                csv_writer = csv.writer(csv_file)
+                csv_writer.writerow(header_elements)
+                csv_writer.writerows(lines)
+
+    def get_train_task_name_by_estimator_id(self, estimator_id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT train_task_name FROM common_fraud.train_task t join common_fraud.estimator e on t.id = e.train_task_id WHERE e.id = %s"
+        parameter = (estimator_id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            return query_result[0] if query_result else None
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def check_if_any_train_in_progress(self):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.train_task WHERE progress_status = %s"
+        parameter = (IN_PROGRESS,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            if query_result:
+                result = True
+            else:
+                result = False
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_existing_planned_encoding_and_feature_engineering_id(self):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        result = list()
+        sql_select_query = "select id from common_fraud.planned_encoding_and_feature_engineering"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+        for item in query_result:
+            result.append(item[0])
+        return result
+
+    def get_parameters_from_plan_by_id(self, id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.planned_encoding_and_feature_engineering WHERE id = %s"
+        parameter = (id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+        schema_name = query_result[1]
+        table_name = query_result[2]
+        detailed_information_about_table = json.loads(query_result[3])
+        time_base_field_name = query_result[4]
+        encoding_parameters = json.loads(query_result[5])
+        feature_engineering_parameters = json.loads(query_result[6])
+        return schema_name, table_name, detailed_information_about_table, time_base_field_name, encoding_parameters, feature_engineering_parameters
+
+    def get_raw_dataset_id(self, schema_name, table_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.raw_dataset WHERE schema_name = %s and table_name = %s"
+        parameter = (schema_name, table_name)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            if query_result is not None:
+                id = query_result[0]
+            else:
+                connection_object = self.connection_pool.get_connection()
+                cursor = connection_object.cursor()
+                sql_insert_command = "INSERT INTO  common_fraud.raw_dataset (schema_name, table_name) VALUES(%s, %s)"
+                parameter = (schema_name, table_name)
+                cursor.execute(sql_insert_command, parameter)
+                connection_object.commit()
+                id = cursor.lastrowid
+                cursor.close()
+                connection_object.close()
+            return id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_encoding_id_by_json_content(self, encoding_parameters):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.encoding"
+        try:
+            id = None
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            for item in query_result:
+                if json.loads(item[1]) == encoding_parameters:
+                    id = item[0]
+            if id is None:
+                connection_object = self.connection_pool.get_connection()
+                cursor = connection_object.cursor()
+                sql_insert_command = "INSERT INTO  common_fraud.encoding (encoding_parameters) VALUES(%s)"
+                parameter = (json.dumps(encoding_parameters),)
+                cursor.execute(sql_insert_command, parameter)
+                connection_object.commit()
+                id = cursor.lastrowid
+                cursor.close()
+                connection_object.close()
+            return id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_encoded_table_properties(self, raw_dataset_id, encoding_id, time_base_field_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.encoded_table_registry where raw_dataset_id = %s and encoding_id = %s and time_base_field_name = %s"
+        try:
+            parameter = (raw_dataset_id, encoding_id, time_base_field_name)
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            id = None
+            encoded_table_name = None
+            encoded_field_names = None
+            label_encoder_registry = None
+            if query_result is not None:
+                id = query_result[0]
+                encoded_table_name = query_result[4]
+                encoded_field_names = json.loads(query_result[5])
+                label_encoder_registry = json.loads(query_result[6])
+            return id, encoded_table_name, encoded_field_names, label_encoder_registry
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_feature_engineering_id_by_json_content(self, feature_engineering_parameters):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.feature_engineering"
+        try:
+            id = None
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            for item in query_result:
+                if json.loads(item[1]) == feature_engineering_parameters:
+                    id = item[0]
+            if id is None:
+                connection_object = self.connection_pool.get_connection()
+                cursor = connection_object.cursor()
+                sql_insert_command = "INSERT INTO  common_fraud.feature_engineering (feature_engineering_parameters) VALUES(%s)"
+                parameter = (json.dumps(feature_engineering_parameters),)
+                cursor.execute(sql_insert_command, parameter)
+                connection_object.commit()
+                id = cursor.lastrowid
+                cursor.close()
+                connection_object.close()
+            return id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def encode(self, raw_dataset_id, schema_name, table_name, encoding_id, encoding_parameters, time_base_field_name):
+
+        detailed_information_about_table = self.get_detailed_information_about_table(schema_name, table_name)
+        field_types_by_name = self.get_field_types_by_name(schema_name, table_name)
+        original_field_names = list(field_types_by_name.keys())
+        original_data_set = self.get_record_for_processing(schema_name, table_name, time_base_field_name)
+        encoder = encoding_module.DataBaseEncoder(original_data_set, original_field_names)
+        encoded_array, encoder_by_field_name, encoded_field_names = encoder.encode(encoding_parameters,
+                                                                                   detailed_information_about_table)
+        encoded_table_name = table_name + UNDER_SCORE + ENCODED + UNDER_SCORE + str(encoding_id)
+        ddl_command_builder = ddl_build_module.DdlCommandBuilder()
+        encoded_table_create_script = ddl_command_builder.build_create_encoded_or_engineered_table_script(schema_name,
+                                                                                                          encoded_table_name,
+                                                                                                          encoded_field_names)
+        self.create_encoded_or_engineered_table(encoded_table_create_script)
+        insert_script = ddl_command_builder.create_insert_into_encoded_or_feature_engineered_script(schema_name,
+                                                                                                    encoded_table_name,
+                                                                                                    encoded_field_names)
+        self.persist_encoded_or_engineered_dataset(insert_script, encoded_array, type="Encoded")
+        label_encoder_registry = dict()
+        for field_name, label_encoder in encoder_by_field_name.items():
+            id_in_label_encoder_table = self.persist_label_encoder(field_name, label_encoder)
+            label_encoder_registry[field_name] = id_in_label_encoder_table
+        encoded_table_registry_id = self.persist_encoded_table_registry(raw_dataset_id, encoding_id,
+                                                                        time_base_field_name, encoded_table_name,
+                                                                        encoded_field_names, label_encoder_registry)
+        return encoded_array, encoded_table_registry_id, encoded_table_name, encoded_field_names, label_encoder_registry
+
+    def get_field_types_by_name(self, schema_name, table_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT COLUMN_NAME, DATA_TYPE  FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schema_name}' AND TABLE_NAME = '{table_name}' ORDER BY ordinal_position"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            field_types_by_name = dict()
+            for column_name_and_type in query_result:
+                field_name = column_name_and_type[0]
+                field_type = column_name_and_type[1].decode("utf-8")
+                field_types_by_name[field_name] = field_type
+            self.logger.debug(f"field types by name in {schema_name} schema, {table_name} table: {field_types_by_name}")
+            return field_types_by_name
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_field_name_in_ordinal_position(self, schema_name, table_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schema_name}' AND TABLE_NAME = '{table_name}' ORDER BY ordinal_position"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            field_names = list()
+            for item in query_result:
+                field_names.append(item[0])
+            self.logger.debug(f"field names in {schema_name} schema, {table_name} table: {field_names}")
+            return field_names
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_field_type_in_ordinal_position(self, schema_name, table_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schema_name}' AND TABLE_NAME = '{table_name}' ORDER BY ordinal_position"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            field_names = list()
+            for item in query_result:
+                field_names.append(item[0].decode("utf-8"))
+            self.logger.debug(f"field types in {schema_name} schema, {table_name} table: {field_names}")
+            return field_names
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_record_for_processing(self, schema_name, table_name, used_time_base_field):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT * FROM {schema_name}.{table_name} ORDER BY {used_time_base_field} DESC"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            result = np.array(query_result)
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def persist_encoded_or_engineered_dataset(self, script, dataset, type):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        length_of_dataset = len(dataset)
+        bound = 100
+        try:
+            if length_of_dataset > bound:
+                number_of_part_array = int(length_of_dataset / bound)
+                number_of_rest_datas = length_of_dataset - number_of_part_array * bound
+                for i in range(0, number_of_part_array, 1):
+                    temp_array = dataset[i * bound:(i + 1) * bound, :]
+                    value_list = list()
+                    for record in temp_array:
+                        value_list.append(tuple(record))
+                    cursor.executemany(script, value_list)
+                    connection_object.commit()
+                temp_array = dataset[
+                             (number_of_part_array) * bound:(number_of_part_array) * bound + number_of_rest_datas, :]
+                value_list = list()
+                for record in temp_array:
+                    value_list.append(tuple(record))
+                cursor.executemany(script, value_list)
+                connection_object.commit()
+            else:
+                value_list = list()
+                for record in dataset:
+                    value_list.append(tuple(record))
+                cursor.executemany(script, value_list)
+                connection_object.commit()
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(f"{type} database persisted")
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_table_name_from_feature_engineering_registry(self, encoded_table_registry_id, feature_engineering_id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.feature_engineered_table_registry WHERE encoded_table_registry_id = %s and feature_engineering_id = %s"
+        try:
+            parameter = (encoded_table_registry_id, feature_engineering_id)
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            feature_engineered_table_name = None
+            feature_engineered_insert_script = None
+            if query_result is not None:
+                feature_engineered_table_name = query_result[3]
+                feature_engineered_insert_script = query_result[4]
+            return feature_engineered_table_name, feature_engineered_insert_script
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def create_encoded_or_engineered_table(self, script):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        try:
+            cursor.execute(script)
+            connection_object.commit()
+            cursor.close()
+            connection_object.close()
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_records_for_processing(self, schema_name, table_name, time_stamp_field_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT * FROM {schema_name}.{table_name} ORDER BY {time_stamp_field_name} DESC"
+        try:
+            cursor.execute(sql_select_query)
+            result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            records_with_id = np.array(result)
+            records_without_id = records_with_id[:, 1:]
+            return records_without_id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def persist_label_encoder(self, field_name, encoder):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_command = "INSERT INTO common_fraud.label_encoder (encoder_object) values (%s)"
+        pickled_encoder = pickle.dumps(encoder)
+        parameter = (pickled_encoder,)
+        try:
+            cursor.execute(sql_insert_command, parameter)
+            connection_object.commit()
+            last_inserted_row_id = cursor.lastrowid
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(f"label encoder persisted, field name: {field_name}, id: {id}")
+            return last_inserted_row_id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def persist_encoded_table_registry(self, raw_dataset_id, encoding_id, time_base_field_name, encoded_table_name,
+                                       encoded_field_names,
+                                       label_encoder_registry):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_command = "INSERT INTO common_fraud.encoded_table_registry (raw_dataset_id, encoding_id, time_base_field_name, encoded_table_name, encoded_field_names, label_encoder_registry) values (%s,%s,%s,%s,%s,%s)"
+        parameter = (
+            raw_dataset_id, encoding_id, time_base_field_name, encoded_table_name, json.dumps(encoded_field_names),
+            json.dumps(label_encoder_registry))
+        try:
+            cursor.execute(sql_insert_command, parameter)
+            connection_object.commit()
+            last_inserted_row_id = cursor.lastrowid
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(
+                f"record persisted in the encoded_table_registry table, encoded_table_name: {encoded_table_name}")
+            return last_inserted_row_id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def feature_engineer(self, schema_name, encoded_table_name, fraud_type_field_name, feature_engineering_id, dataset,
+                         time_base_field_name,
+                         encoded_field_names, feature_engineering_parameters, encoded_table_registry_id, used_cpu_core):
+        engineer = engineering_module.Engineer(time_base_field_name, encoded_field_names, fraud_type_field_name,
+                                               feature_engineering_parameters,
+                                               used_cpu_core)
+        feature_engineered_dataset, feature_engineered_field_names = engineer.create_new_features(dataset)
+        feature_engineered_table_name = encoded_table_name + UNDER_SCORE + FE + UNDER_SCORE + str(
+            feature_engineering_id)
+        ddl_command_builder = ddl_build_module.DdlCommandBuilder()
+        feature_engineered_table_create_script = ddl_command_builder.build_create_encoded_or_engineered_table_script(
+            schema_name, feature_engineered_table_name, feature_engineered_field_names)
+        self.create_encoded_or_engineered_table(feature_engineered_table_create_script)
+        feature_engineered_insert_script = ddl_command_builder.create_insert_into_encoded_or_feature_engineered_script(
+            schema_name, feature_engineered_table_name, feature_engineered_field_names)
+        feature_engineered_insert_script_using_in_prediction = ddl_command_builder.create_insert_into_encoded_or_feature_engineered_script_for_prediction(
+            schema_name, feature_engineered_table_name, feature_engineered_field_names, fraud_type_field_name)
+        self.persist_encoded_or_engineered_dataset(feature_engineered_insert_script, feature_engineered_dataset,
+                                                   type="Feature engineered")
+        self.persist_feature_engineered_table_registry_item(encoded_table_registry_id, feature_engineering_id,
+                                                            feature_engineered_table_name,
+                                                            feature_engineered_insert_script)
+        return feature_engineered_dataset, feature_engineered_table_name, feature_engineered_insert_script_using_in_prediction
+
+    def persist_feature_engineered_table_registry_item(self, encoded_table_registry_id, feature_engineering_id,
+                                                       feature_engineered_table_name, feature_engineered_insert_script):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_command = "INSERT INTO common_fraud.feature_engineered_table_registry (encoded_table_registry_id, feature_engineering_id, feature_engineered_table_name, feature_engineered_insert_script) values (%s,%s,%s,%s)"
+        parameter = (encoded_table_registry_id, feature_engineering_id, feature_engineered_table_name,
+                     feature_engineered_insert_script)
+        try:
+            cursor.execute(sql_insert_command, parameter)
+            connection_object.commit()
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(
+                f"record persisted in feature_engineered_table_registry table, feature_engineered_table_name: {feature_engineered_table_name}")
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    #
+    def get_train_parameters(self, train_task_id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "select * from common_fraud.train_task where id = %s"
+        parameter = (train_task_id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def persist_estimator(self, train_task_id, transaction_identifier_field_name, estimator):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_query = "INSERT INTO common_fraud.estimator (train_task_id, transaction_identifier_field_name, estimator_object) VALUES (%s,%s,%s)"
+        pickled_estimator_container = pickle.dumps(estimator)
+        parameter = (train_task_id, transaction_identifier_field_name, pickled_estimator_container)
+        try:
+            cursor.execute(sql_insert_query, parameter)
+            connection_object.commit()
+            last_inserted_row_id = cursor.lastrowid
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(f"estimator persisted, train_task_id: {train_task_id}, id: {last_inserted_row_id}")
+            return last_inserted_row_id
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def persist_metrics(self, estimator_id, TP, FP, TN, FN, sensitivity, specificity, accuracy, balanced_accuracy,
+                        precision, recall, PPV, NPV, FNR, FPR, FDR, FOR, f1, f_05, f2, MCC, ROCAUC, Youdens_statistic):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_insert_query = "INSERt INTO common_fraud.metrics (estimator_id,TP,FP,TN,FN,sensitivity,specificity,accuracy,balanced_accuracy,prec,recall,PPV,NPV,FNR,FPR,FDR,F_OR,f1,f_05,f2,MCC,ROCAUC,Youdens_statistic) VALUES" \
+                           "(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
+        values = (estimator_id, TP, FP, TN, FN, sensitivity, specificity, accuracy, balanced_accuracy,
+                  precision, recall, PPV, NPV, FNR, FPR, FDR, FOR, f1, f_05, f2, MCC, ROCAUC, Youdens_statistic)
+        try:
+            cursor.execute(sql_insert_query, values)
+            connection_object.commit()
+            last_inserted_row_id = cursor.lastrowid
+            cursor.close()
+            connection_object.close()
+            self.logger.debug(f"metrics persisted, estimator_id: {estimator_id}, id: {last_inserted_row_id}")
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_all_metrics(self):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.metrics"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            single_response = self.build_complex_metrics_response(query_result)
+            return single_response
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    # 6
+    def get_metrics_by_id(self, id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.metrics WHERE id = %s"
+        parameter = (id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            complex_response = self.build_complex_metrics_response(query_result)
+            return complex_response
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def build_complex_metrics_response(self, query_result):
+        response = list()
+        for record in query_result:
+            response.append(self.build_simple_metrics_response(record))
+        return response
+
+    def build_simple_metrics_response(self, record):
+        single_result = dict()
+        single_result["id"] = record[0]
+        single_result["estimator_id"] = record[1]
+        single_result["TP"] = record[2]
+        single_result["FP"] = record[3]
+        single_result["TN"] = record[4]
+        single_result["FN"] = record[5]
+        single_result["sensitivity"] = record[6]
+        single_result["specificity"] = record[7]
+        single_result["accuracy"] = record[8]
+        single_result["balanced_accuracy"] = record[9]
+        single_result["precision"] = record[10]
+        single_result["recall"] = record[11]
+        single_result["PPV"] = record[12]
+        single_result["NPV"] = record[13]
+        single_result["FNR"] = record[14]
+        single_result["FPR"] = record[15]
+        single_result["FDR"] = record[16]
+        single_result["FOR"] = record[17]
+        single_result["f1"] = record[18]
+        single_result["f0.5"] = record[19]
+        single_result["f2"] = record[20]
+        single_result["MCC"] = record[21]
+        single_result["ROCAUC"] = record[22]
+        single_result["Youdens_statistic"] = record[23]
+        return single_result
+
+    def get_all_estimator(self):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.estimator"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            response = list()
+            if query_result:
+                response = self.build_estimator_response(query_result)
+            return response
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+        single_response = self.build_complex_metrics_response(query_result)
+        return single_response
+
+    def get_estimator_by_train_task_id(self, train_task_id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT * FROM common_fraud.estimator WHERE train_task_id = %s"
+        parameter = (train_task_id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            response = list()
+            if query_result:
+                response = self.build_estimator_response(query_result)
+            return response
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def build_estimator_response(self, query_result):
+        summarized_result = list()
+        for item in query_result:
+            single_result = dict()
+            single_result["id"] = item[0]
+            single_result["train_task_id"] = item[1]
+            single_result["transaction_identifier_field_name"] = item[2]
+            summarized_result.append(single_result)
+        return summarized_result
+
+    def get_transaction_identifier_field_name_by_estimator_id(self, id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT transaction_identifier_field_name FROM common_fraud.estimator WHERE id = %s"
+        parameter = (id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            return query_result[0] if query_result else None
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_confusion_matrix_elements_by_train_task_id(self, train_task_id):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = "SELECT TP, TN, FP, FN FROM common_fraud.metrics inner join common_fraud.estimator e on metrics.estimator_id = e.id inner join common_fraud.train_task tt on e.train_task_id = tt.id where train_task_id = %s"
+        parameter = (train_task_id,)
+        try:
+            cursor.execute(sql_select_query, parameter)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            response = dict()
+            if query_result:
+                response["TP"] = query_result[0]
+                response["TN"] = query_result[1]
+                response["FP"] = query_result[2]
+                response["FN"] = query_result[3]
+            return response
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_columns_contain_only_distinct_values(self, schema_name, table_name):
+        result = list()
+        column_names = self.get_field_name_in_ordinal_position(schema_name, table_name)
+        count_by_column_name = dict()
+        for column_name in column_names:
+            count = self.get_distinct_count_of_column(schema_name, table_name, column_name)
+            count_by_column_name[column_name] = count
+        detailed_info = self.get_detailed_information_about_table(schema_name, table_name)
+        record_number = detailed_info.get("record_number")
+        for column_name, count in count_by_column_name.items():
+            if count == record_number:
+                result.append(column_name)
+        return result
+
+    def get_distinct_count_of_column(self, schema_name, table_name, column_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT COUNT(DISTINCT {column_name}) FROM {schema_name}.{table_name}"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            count = query_result[0]
+            self.logger.debug(
+                f"Distinct count of values: {count} in {schema_name} schema, {table_name} table: {column_name} column.")
+            return count
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def create_reduced_and_for_evaluation_tables(self, schema_name, table_name, fraud_type_field_name,
+                                                 positive_fraud_case_number, negative_fraud_case_number,
+                                                 date_suffix_to_append):
+        detailed_information_about_table = self.get_detailed_information_about_table(schema_name, table_name)
+        fraud_candidates = detailed_information_about_table.get("fraud_candidates")
+        fraud_candidate_names = list()
+        for fraud_candidate in fraud_candidates:
+            fraud_candidate_name = fraud_candidate.get("name")
+            fraud_candidate_names.append(fraud_candidate_name)
+        if fraud_type_field_name not in fraud_candidate_names:
+            raise IllegalArgumentException({"message": "Fraud type field name not in fraud candidates",
+                                            "parameter_name": fraud_type_field_name})
+        positive_fraud_case_ids = self.get_id_collection_by_column_name_and_value(schema_name, table_name,
+                                                                                  fraud_type_field_name, 1)
+        negative_fraud_case_ids = self.get_id_collection_by_column_name_and_value(schema_name, table_name,
+                                                                                  fraud_type_field_name, 0)
+        random_selected_positive_fraud_case_ids = random.sample(positive_fraud_case_ids, k=positive_fraud_case_number)
+        random_selected_negative_fraud_case_ids = random.sample(negative_fraud_case_ids, k=negative_fraud_case_number)
+        records_by_id = self.get_records(schema_name, table_name)
+        field_names_in_ordinal_position = self.get_field_name_in_ordinal_position(schema_name, table_name)
+        fraud_field_index = field_names_in_ordinal_position.index(fraud_type_field_name) - 1
+        reduced_records = list()
+        for_evaluation_records = list()
+        simulated_real_recods = list()
+        for id, record in records_by_id.items():
+            if id not in random_selected_positive_fraud_case_ids and id not in random_selected_negative_fraud_case_ids:
+                reduced_records.append(record)
+        for id, record in records_by_id.items():
+            if id in random_selected_negative_fraud_case_ids:
+                fraud_field_value = record.pop(fraud_field_index)
+                for_evaluation_record = [id]
+                for_evaluation_record.extend(record)
+                for_evaluation_records.append(for_evaluation_record)
+                simulated_real_recods.append([id, fraud_field_value])
+        for id, record in records_by_id.items():
+            if id in random_selected_positive_fraud_case_ids:
+                fraud_field_value = record.pop(fraud_field_index)
+                for_evaluation_record = [id]
+                for_evaluation_record.extend(record)
+                for_evaluation_records.append(for_evaluation_record)
+                simulated_real_recods.append([id, fraud_field_value])
+        field_name_and_type_collection = detailed_information_about_table.get("fields")
+        primary_key_field_name = detailed_information_about_table.get("primary_key")
+        field_type_by_name_for_evaluation = dict()
+        field_type_by_name_reduced = dict()
+        field_type_by_name_simulated_real = dict()
+        for item in field_name_and_type_collection:
+            field_name = item.get("name")
+            field_type = item.get("type")
+            if field_name == primary_key_field_name:
+                field_type_by_name_simulated_real["transaction_identifier"] = field_type
+            else:
+                if field_type == "varchar":
+                    field_type += "(255)"
+                if field_name == fraud_type_field_name:
+                    field_type_by_name_reduced[field_name] = field_type
+                    field_type_by_name_simulated_real[field_name] = field_type
+                else:
+                    field_type_by_name_reduced[field_name] = field_type
+                    field_type_by_name_for_evaluation[field_name] = field_type
+        reduced_table_name = table_name + "_reduced"
+        for_evaluation_table_name = table_name + "_for_evaluation"
+        simulated_real_table_name = table_name + "_simulated_real"
+        if date_suffix_to_append:
+            now = datetime.datetime.now()
+            date_and_time = now.strftime("%Y%m%d_%H%M%S")
+            date_time_suffix = UNDER_SCORE + str(date_and_time)
+            reduced_table_name += date_time_suffix
+            for_evaluation_table_name += date_time_suffix
+            simulated_real_table_name += date_time_suffix
+        self.create_train_table(schema_name, reduced_table_name, field_type_by_name_reduced,
+                                is_id_auto_incremented=True)
+        self.create_train_table(schema_name, for_evaluation_table_name, field_type_by_name_for_evaluation,
+                                is_id_auto_incremented=False)
+        self.create_train_table(schema_name, simulated_real_table_name, field_type_by_name_simulated_real,
+                                is_id_auto_incremented=True)
+
+        reduced_records_array = np.array(reduced_records)
+        for_evaluated_records_array = np.array(for_evaluation_records)
+        simulated_real_records_array = np.array(simulated_real_recods,dtype='O')
+        ddl_command_builder = ddl_build_module.DdlCommandBuilder()
+        reduced_records_insert_script = ddl_command_builder.create_insert_into_generic_train_database_script(
+            schema_name, reduced_table_name, field_type_by_name_reduced, is_id_auto_incremented=True)
+        for_evaluated_records_insert_script = ddl_command_builder.create_insert_into_generic_train_database_script(
+            schema_name, for_evaluation_table_name, field_type_by_name_for_evaluation, is_id_auto_incremented=False)
+        simulated_real_recods_insert_script = ddl_command_builder.create_insert_into_generic_train_database_script(
+            schema_name, simulated_real_table_name, field_type_by_name_simulated_real, is_id_auto_incremented=True)
+
+        self.persist_encoded_or_engineered_dataset(reduced_records_insert_script, reduced_records_array, REDUCED)
+        self.persist_encoded_or_engineered_dataset(for_evaluated_records_insert_script, for_evaluated_records_array,
+                                                   FOR_EVAULATED)
+        self.persist_encoded_or_engineered_dataset(simulated_real_recods_insert_script, simulated_real_records_array,
+                                                   SIMULATED_REAL)
+        planned_csv_content = dict()
+        for record in simulated_real_recods:
+            planned_csv_content[record[0]] = record[1]
+        csv_content_and_file_name = dict()
+        csv_content_and_file_name["planned_csv_content"] = planned_csv_content
+        csv_content_and_file_name["planned_csv_file_name"] = for_evaluation_table_name
+        return csv_content_and_file_name
+
+    def get_id_collection_by_column_name_and_value(self, schema_name, table_name, column_name, value):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT id FROM {schema_name}.{table_name} WHERE {column_name} = {value}"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchall()
+            cursor.close()
+            connection_object.close()
+            result = list()
+            for item in query_result:
+                result.append(item[0])
+            return result
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
+
+    def get_generic_records_for_evaluation(self, schema_name, table_name):
+        field_names = self.get_field_name_in_ordinal_position(schema_name, table_name)
+        field_types = self.get_field_type_in_ordinal_position(schema_name, table_name)
+        detailed_information = self.get_detailed_information_about_table(schema_name, table_name)
+        primary_key_field_name = detailed_information.get("primary_key")
+        record_by_id = self.get_records(schema_name, table_name)
+        records_in_string_format = list()
+        for id, record in record_by_id.items():
+            fields_as_str = list()
+            fields_as_str.append(id)
+            for field in record:
+                fields_as_str.append(str(field))
+            records_in_string_format.append(fields_as_str)
+        result = dict()
+        result["field_types"] = field_types
+        result["field_names"] = field_names
+        result["records"] = records_in_string_format
+        return result
+
+    def get_max_retrospective_day(self,schema_name,table_name,time_base_field_name):
+        connection_object = self.connection_pool.get_connection()
+        cursor = connection_object.cursor()
+        sql_select_query = f"SELECT DATEDIFF(max({time_base_field_name}),min({time_base_field_name}))id FROM {schema_name}.{table_name}"
+        try:
+            cursor.execute(sql_select_query)
+            query_result = cursor.fetchone()
+            cursor.close()
+            connection_object.close()
+            return query_result[0]
+        except mysql.connector.Error as err:
+            self.logger.error(f"MySQL error message: {err.msg}")
\ No newline at end of file
diff --git a/Train/src/ddl_build_module.py b/Train/src/ddl_build_module.py
new file mode 100644 (file)
index 0000000..d0387ce
--- /dev/null
@@ -0,0 +1,127 @@
+import logging
+
+import exception_module
+import re
+
+COMMA = ","
+SPACE = " "
+NEW_LINE = "\n"
+BEGIN_BRACKET = "("
+END_BRACKET = ")"
+DOT = "."
+
+
+class DdlCommandBuilder:
+    """DDL"""
+    def __init__(self):
+        self.logger = logging.getLogger("train.server.database.ddl")
+
+    def build_create_encoded_or_engineered_table_script(self, schema_name, table_name, field_names):
+        script = "CREATE TABLE IF NOT EXISTS" + SPACE + schema_name + DOT + table_name
+        script += BEGIN_BRACKET
+        script += "id BIGINT NOT NULL AUTO_INCREMENT" + COMMA
+        for field_name in field_names:
+            script += field_name + SPACE + "DOUBLE PRECISION" + COMMA
+        script += "PRIMARY KEY (id)"
+        script += END_BRACKET + SPACE
+        script += "ENGINE = InnoDB"
+        return script
+
+    def create_insert_into_encoded_or_feature_engineered_script(self, schema_name, table_name, field_names):
+        script = "INSERT INTO" + SPACE + schema_name + DOT + table_name + SPACE
+        script += BEGIN_BRACKET
+        field_number = len(field_names)
+        for i in range(field_number):
+            script += field_names[i]
+            if i != field_number - 1:
+                script += COMMA
+        script += END_BRACKET + SPACE
+        script += "VALUES"
+        script += BEGIN_BRACKET
+        for i in range(field_number):
+            script += "%s"
+            if i != field_number - 1:
+                script += COMMA
+        script += END_BRACKET
+        return script
+
+    def create_insert_into_encoded_or_feature_engineered_script_for_prediction(self, schema_name, table_name,
+                                                                               field_names,
+                                                                               fraud_type_field_name):
+        script = "INSERT INTO" + SPACE + schema_name + DOT + table_name + SPACE
+        script += BEGIN_BRACKET
+        field_names.remove(fraud_type_field_name)
+        field_number = len(field_names)
+        for i in range(field_number):
+            script += field_names[i]
+            if i != field_number - 1:
+                script += COMMA
+        script += END_BRACKET + SPACE
+        script += "VALUES"
+        script += BEGIN_BRACKET
+        for i in range(field_number):
+            script += "%s"
+            if i != field_number - 1:
+                script += COMMA
+        script += END_BRACKET
+        return script
+
+    def build_create_generic_train_database_script(self, schema_name, table_name, field_type_by_name,
+                                                   is_id_auto_incremented):
+        possible_field_types_without_varchar = ["BIGINT", "INT", "DOUBLE PRECISION", "FLOAT", "DATE", "TIME",
+                                                "DATETIME", "TIMESTAMP", "YEAR"]
+        script = "CREATE TABLE IF NOT EXISTS" + SPACE + schema_name + DOT + table_name
+        script += BEGIN_BRACKET
+        script += "id BIGINT NOT NULL"
+        script += SPACE
+        if is_id_auto_incremented:
+            script += "AUTO_INCREMENT"
+        script += COMMA
+        for field_name, field_type in field_type_by_name.items():
+            upper_case_field_type = field_type.upper().strip()
+            is_regular_varchar = re.search("^VARCHAR\([0-9]+\)", upper_case_field_type)
+            if upper_case_field_type not in possible_field_types_without_varchar and is_regular_varchar is None:
+                raise exception_module.DdlBuildException(
+                    f"invalid field type, field type name: {upper_case_field_type}")
+
+            if upper_case_field_type.startswith("VARCHAR"):
+                try:
+                    begin_bracket_index = upper_case_field_type.index("(")
+                    end_brack_index = upper_case_field_type.index(")")
+                    varchar_length_as_string = upper_case_field_type[begin_bracket_index + 1:end_brack_index]
+                    int(varchar_length_as_string)
+                except ValueError as e:
+                    raise exception_module.DdlBuildException("varchar size parameter isn't int type")
+            if upper_case_field_type.startswith("DATETIME"):
+                upper_case_field_type += "(6)"
+            script += field_name + SPACE + upper_case_field_type + COMMA
+        script += "PRIMARY KEY (id)"
+        script += END_BRACKET + SPACE
+        script += "ENGINE = InnoDB"
+        return script
+
+    def create_insert_into_generic_train_database_script(self, schema_name, table_name, field_type_by_name,
+                                                         is_id_auto_incremented):
+        script = "INSERT INTO" + SPACE + schema_name + DOT + table_name + SPACE
+        script += BEGIN_BRACKET
+        if not is_id_auto_incremented:
+            script += "id"
+            script += COMMA
+        field_names = list(field_type_by_name.keys())
+        field_number = len(field_names)
+        for i in range(field_number):
+            script += field_names[i]
+            if i != field_number - 1:
+                script += COMMA
+        script += END_BRACKET + SPACE
+        script += "VALUES"
+        script += BEGIN_BRACKET
+        if not is_id_auto_incremented:
+            script += "%s"
+            script += COMMA
+        for i in range(field_number):
+            script += "%s"
+            if i != field_number - 1:
+                script += COMMA
+        script += END_BRACKET
+        return script
diff --git a/Train/src/encoding_module.py b/Train/src/encoding_module.py
new file mode 100644 (file)
index 0000000..322ea80
--- /dev/null
@@ -0,0 +1,224 @@
+import logging
+import pickle
+
+import numpy as np
+import pandas as pd
+from sklearn.preprocessing import LabelEncoder
+from sklearn.utils import column_or_1d
+
+PRIMARY_KEY = "primary_key"
+OMIT = "omit"
+FRAUD_TYPE = "fraud_type"
+JULIAN = "julian"
+LABEL_ENCODER = "label_encoder"
+FLOAT = "float"
+INT = "int"
+
+
+class NotImplementedEncodingException(Exception):
+    pass
+
+
+class DataBaseEncoder:
+    def __init__(self, dataset, original_fields):
+        self.logger = logging.getLogger("train.server.database.encoding")
+        self.original_dataset = dataset
+        self.original_fields = original_fields
+
+    def encode(self, encoding_parameters, detailed_information_about_table):
+        primary_key_field_name = detailed_information_about_table.get(PRIMARY_KEY)
+        encoder_by_field_name = dict()
+        encoded_fields = self.build_encoded_field_names(encoding_parameters, primary_key_field_name)
+        number_of_records = len(self.original_dataset)
+        number_of_fields = len(encoded_fields)
+        encoded_array = np.empty([number_of_records, number_of_fields])
+        for field in encoded_fields:
+            field_type_before_encoding = self.get_field_type_before_encoding(field, detailed_information_about_table)
+            column_index_in_original_dataset = self.original_fields.index(field)
+            column_index_in_encoded_array = encoded_fields.index(field)
+            encoding_parameter = encoding_parameters.get(field)
+            if encoding_parameter is None:
+                if self.is_num_type(field_type_before_encoding):
+                    encoded_array[:,
+                    column_index_in_encoded_array: column_index_in_encoded_array + 1] = self.original_dataset[:,
+                                                                                        column_index_in_original_dataset: column_index_in_original_dataset + 1]
+                    continue
+            elif encoding_parameter == FRAUD_TYPE:
+                encoded_array[:,
+                column_index_in_encoded_array: column_index_in_encoded_array + 1] = self.original_dataset[:,
+                                                                                    column_index_in_original_dataset: column_index_in_original_dataset + 1]
+                continue
+            elif encoding_parameter == JULIAN:
+                encoded_array[:,
+                column_index_in_encoded_array: column_index_in_encoded_array + 1] = self.convert_timestamp_to_julian(column_index_in_original_dataset)
+                continue
+            elif encoding_parameter == LABEL_ENCODER:
+                converted_column, encoder = self.convert_with_label_encoder(column_index_in_original_dataset)
+                encoded_array[:, column_index_in_encoded_array: column_index_in_encoded_array + 1] = converted_column
+                encoder_by_field_name[field] = encoder
+                continue
+            elif encoding_parameter == FLOAT:
+                encoded_array[:,
+                column_index_in_encoded_array: column_index_in_encoded_array + 1] = self.convert_string_to_float(column_index_in_original_dataset)
+                continue
+            elif encoding_parameter == INT:
+                encoded_array[:,
+                column_index_in_encoded_array: column_index_in_encoded_array + 1] = self.convert_string_to_int(column_index_in_original_dataset)
+                continue
+            else:
+                raise NotImplementedEncodingException(
+                    {"message": "The given encoding not implemented", "field_name": field,
+                     "encoding type": encoding_parameter})
+        return encoded_array, encoder_by_field_name, encoded_fields
+
+    # 3.1.5.1
+    def build_encoded_field_names(self, encoding_parameters, primary_key_field_name):
+        feature_fields = list()
+        fraud_type_field = None
+        number_of_original_fields = len(self.original_fields)
+        for index in range(number_of_original_fields):
+            field = self.original_fields[index]
+            if field == primary_key_field_name:
+                continue  # id-re nincs szükség auto-increment miatt
+            elif encoding_parameters.get(field) == FRAUD_TYPE:
+                fraud_type_field = field
+                continue
+            elif encoding_parameters.get(field) == OMIT:
+                continue  # elhagyandó
+            feature_fields.append(field)
+        if fraud_type_field is not None:
+            feature_fields.append(fraud_type_field)
+        return feature_fields
+
+    def get_field_type_before_encoding(self, field, detailed_information_about_table):
+        result = None
+        for field_property in detailed_information_about_table.get("fields"):
+            if field == field_property.get("name"):
+                result = field_property.get("type")
+        return result
+
+    def is_num_type(self, item):
+        integer_types = ("integer", "int", "smallint")
+        float_types = ("float", "double")
+        if item in integer_types or item in float_types:
+            return True
+        else:
+            return False
+
+    def convert_timestamp_to_julian(self, original_index):
+        converted_time_stamp_datas = list()
+        for timestamp in self.original_dataset[:, original_index:original_index + 1]:
+            t = timestamp[0]
+            ts = pd.Timestamp(t)
+            converted_time_stamp_to_julian = ts.to_julian_date()
+            converted_time_stamp_datas.append(converted_time_stamp_to_julian)
+        converted_time_stamp_data_array = np.array(converted_time_stamp_datas)
+        reshaped_array = converted_time_stamp_data_array.reshape(-1, 1)
+        return reshaped_array
+
+    def convert_with_label_encoder(self, original_index):
+        strings = self.original_dataset[:, original_index:original_index + 1]
+        encoder = LabelEncoder()
+        modified_strings = column_or_1d(strings)
+        encoder.fit(modified_strings)
+        transformed_array = encoder.transform(modified_strings)
+        reshaped_transformed_array = transformed_array.reshape(-1, 1)
+        return reshaped_transformed_array, encoder
+
+    def convert_string_to_float(self, original_index):
+        strings = self.original_dataset[:, original_index:original_index + 1]
+        floats = np.array(strings, dtype=float)
+        reshaped_array = floats.reshape(-1, 1)
+        return reshaped_array
+
+    def convert_string_to_int(self, original_index):
+        strings = self.original_dataset[:, original_index:original_index + 1]
+        ints = np.array(strings, dtype=int)
+        reshaped_array = ints.reshape(-1, 1)
+        return reshaped_array
+
+    #  új metódusok vége
+
+    def convertCardNumberStringToFloat(self, array):
+        cardNumberStrings = array[:, 1:2]
+        # cardEncoder=LabelEncoder()
+        # modifiedCardNumbers=column_or_1d(cardNumberStrings)
+        # cardEncoder.fit(modifiedCardNumbers)
+        # encodedCardNumbers=cardEncoder.transform(modifiedCardNumbers)
+        # reshapedCardNumbers=encodedCardNumbers.reshape(-1,1)
+        cardNumberFloats = np.array(cardNumberStrings, dtype=float)
+        reshapedCardNumberIntegers = cardNumberFloats.reshape(-1, 1)
+        array[:, 1:2] = reshapedCardNumberIntegers
+        # array[:,1:2]=reshapedCardNumbers
+
+    def convertVendorCodeStringToFloat(self, array):
+        vendorCodeStrings = array[:, 8:9]
+        vendorCodeIntegers_as_int_type = vendorCodeStrings.astype(np.int)
+        vendorCodeIntegers_as_float_type = vendorCodeStrings.astype(np.float)
+        vendorCodeIntegers = np.array(vendorCodeStrings, dtype=int)
+        reshapedVendorCodeIntegers = vendorCodeIntegers.reshape(-1, 1)
+        array[:, 8:9] = reshapedVendorCodeIntegers
+
+    def convertCountryFeature(self, array):
+        countries = array[:, 7:8]
+        countryEncoder = LabelEncoder()
+        modifiedCountries = column_or_1d(countries)
+        countryEncoder.fit(modifiedCountries)
+        encodedCountries = countryEncoder.transform(modifiedCountries)
+        reshapedEncodedCountries = encodedCountries.reshape(-1, 1)
+        array[:, 7:8] = reshapedEncodedCountries
+        return countryEncoder
+
+    def convertCurrencyFeature(self, array):
+        currenciesArray = array[:, 5:6]
+        currencyEncoder = LabelEncoder()
+        currencyEncoder.fit(currenciesArray)
+        encodedCurrencies = currencyEncoder.transform(currenciesArray)
+        reshapedEncodedCurrencies = encodedCurrencies.reshape(-1, 1)
+        array[:, 5:6] = reshapedEncodedCurrencies
+        return currencyEncoder
+
+    def saveData(self, dataSet, connection, dataBaseName):
+        valuesArray = dataSet[:, 1:]
+        cursor = connection.cursor()
+        sqlUseQuery = "USE " + dataBaseName
+        cursor.execute(sqlUseQuery)
+        sqlInsertQuery = "INSERt INTO encoded_transaction (card_number,transaction_type,timestamp,amount,currency_name,response_code,country_name,vendor_code,fraud) VALUES " \
+                         "(%s,%s,%s,%s,%s,%s,%s,%s,%s)"
+        length = len(valuesArray)
+        bound = 1000
+        if length > bound:
+            numberOfPartArray = int(length / bound)
+            numberOfRestDatas = length - numberOfPartArray * bound
+            for i in range(0, numberOfPartArray, 1):
+                tempArray = valuesArray[i * bound:(i + 1) * bound, :]
+                valueList = list()
+                for record in tempArray:
+                    valueList.append(tuple(record))
+                cursor.executemany(sqlInsertQuery, valueList)
+                connection.commit()
+            tempArray = valuesArray[(numberOfPartArray) * bound:(numberOfPartArray) * bound + numberOfRestDatas, :]
+            valueList = list()
+            for record in tempArray:
+                valueList.append(tuple(record))
+            cursor.executemany(sqlInsertQuery, valueList)
+            connection.commit()
+        else:
+            valueList = list()
+            for record in valuesArray:
+                valueList.append(tuple(record))
+            cursor.executemany(sqlInsertQuery, valueList)
+            connection.commit()
+        cursor.close()
+
+    def saveEncoder(self, connection, databaseName):
+        cursor = connection.cursor()
+        sqlUseQuery = "USE " + databaseName
+        cursor.execute(sqlUseQuery)
+        sqlInsertQuery = "INSERT INTO encoder (encoder_name,encoder_object) VALUES (%s,%s)"
+        pickledCurrencyEncoder = pickle.dumps(self.currencyEncoder)
+        cursor.execute(sqlInsertQuery, ("currency_encoder", pickledCurrencyEncoder))
+        pickledCountryEncoder = pickle.dumps(self.countryEncoder)
+        cursor.execute(sqlInsertQuery, ("country_encoder", pickledCountryEncoder))
+        connection.commit()
+        cursor.close()
diff --git a/Train/src/engineering_module.py b/Train/src/engineering_module.py
new file mode 100644 (file)
index 0000000..c9756fb
--- /dev/null
@@ -0,0 +1,325 @@
+import logging
+import math
+import statistics
+import multiprocessing as mp
+from multiprocessing import Process, Pool
+import numpy as np
+
+CARD_NUMBER = "card_number"
+UNDER_SCORE = "_"
+
+
+class Engineer:
+    def __init__(self, time_base_field_name, encoded_field_names, fraud_type_field_name,feature_engineering_parameters, used_cpu_core):
+        self.logger = logging.getLogger("train.server.database.engineer")
+        self.time_base_field_name = time_base_field_name
+        self.encoded_field_names = encoded_field_names
+        self.fraud_type_field_name=fraud_type_field_name
+        self.feature_engineering_parameters = feature_engineering_parameters
+        self.chosen_feature_field_name = self.feature_engineering_parameters.get("chosen_feature_field")
+        self.intervals = self.feature_engineering_parameters.get("intervals")
+        self.used_cpu_core = used_cpu_core
+
+    # 3.1.7
+    def create_new_features(self, dataset):
+        self.logger.info(f"Used CPU logical core number: {self.used_cpu_core}")
+        feature_engineered_field_names = self.crete_feature_engineered_field_names(self.encoded_field_names,
+                                                                                   self.chosen_feature_field_name,
+                                                                                   self.intervals)
+        transaction_by_card_number = self.get_transaction_by_card_number(dataset, self.encoded_field_names)
+        input_collection = list()
+        for card_number in transaction_by_card_number:
+            input_collection.append(transaction_by_card_number.get(card_number))
+        results_from_threads = list()
+        with Pool(processes=self.used_cpu_core) as pool:
+            results_from_threads = pool.map(self.process_dataset_of_single_cardnumber, input_collection)
+            self.logger.info(f"Number of sublists from threads: {len(results_from_threads)}")
+        num_of_sub_results=len(results_from_threads)
+        concatenated_results=np.empty([0,0])
+        for i in range(num_of_sub_results):
+            if i==0:
+                concatenated_results=results_from_threads[i]
+            else:
+                concatenated_results=np.concatenate((concatenated_results,results_from_threads[i]),axis=0)
+
+        return concatenated_results, feature_engineered_field_names
+
+    def crete_feature_engineered_field_names(self, field_names, chosen_feature_field_name, time_intervals):
+        result = list()
+        real_fraud_field_name=None
+        for field_name in field_names:
+            if field_name==self.fraud_type_field_name:
+                real_fraud_field_name=field_name
+            else:
+                result.append(field_name)
+        for interval in time_intervals:
+            result.append(chosen_feature_field_name + "_current_per_average_" + str(interval))
+            result.append(chosen_feature_field_name + "_current_minus_average_" + str(interval))
+            result.append(chosen_feature_field_name + "_current_per_median_" + str(interval))
+            result.append(chosen_feature_field_name + "_current_minus_median_" + str(interval))
+            result.append(
+                chosen_feature_field_name + "_current_minus_average_" + str(interval) + "_per_deviation_" + str(
+                    interval))
+            result.append(
+                chosen_feature_field_name + "_current_minus_average_" + str(interval) + "_minus_deviation_" + str(
+                    interval))
+            result.append("tr_nr_per_avg_nr_" + str(interval))
+            result.append("tr_nr_minus_avg_nr_" + str(interval))
+            result.append("tr_nr_per_median_nr_" + str(interval))
+            result.append("tr_nr_minus_median_nr_" + str(interval))
+            result.append("avg_tr_interval_per_tr_interval_" + str(interval))
+            result.append("avg_tr_interval_minus_tr_interval_" + str(interval))
+            result.append("median_tr_interval_per_tr_interval_" + str(interval))
+            result.append("median_tr_interval_minus_tr_interval_" + str(interval))
+        result.append(real_fraud_field_name)
+        return result
+
+    def get_transaction_by_card_number(self, dataset, encoded_field_names):
+        result = dict()
+        for record in dataset:
+            index = encoded_field_names.index(CARD_NUMBER)
+            card_number = record[index]
+            if result.get(card_number) is None:
+                transactions = list()
+                transactions.append(record.tolist())
+                result[card_number] = transactions
+            else:
+                transactions = result.get(card_number)
+                transactions.append(record.tolist())
+        return result
+
+    def process_dataset_of_single_cardnumber(self, input_data_of_given_card_number):
+        number_of_new_feature = len(self.intervals) * 14
+        extended_dataset = list()
+        data_set = np.array(input_data_of_given_card_number)
+        transaction_features = data_set[:, :-1]
+        transaction_labels = data_set[:, -1:]
+        length = len(transaction_features)
+        for i in range(length):
+            transaction_feature = transaction_features[i]
+            index_of_feature_to_be_engineered = self.encoded_field_names.index(self.chosen_feature_field_name)
+            feature_value = transaction_feature[index_of_feature_to_be_engineered]
+            index_of_time_stamp = self.encoded_field_names.index(self.time_base_field_name)
+            current_time_stamp = transaction_feature[index_of_time_stamp]
+            transaction_feature_list = list(transaction_feature)
+
+            if i == length - 1:
+                for j in range(number_of_new_feature):
+                    transaction_feature_list.append(0)
+                extended_dataset.append(transaction_feature_list)
+            else:
+                feature_to_be_engineered_by_time_stamp, time_stamps = self.get_feature_to_be_engineered_by_timestamp_before_given_time_stamp(
+                    transaction_features, current_time_stamp, index_of_feature_to_be_engineered, index_of_time_stamp)
+
+                generated_features = list()
+
+                generated_features_based_on_chosen_feature = self.get_generated_features_based_on_chosen_feature(
+                    current_time_stamp, feature_value, feature_to_be_engineered_by_time_stamp)
+
+                generated_features_based_on_transaction_number = self.get_generated_features_based_on_transaction_number(
+                    current_time_stamp, time_stamps)
+
+                generated_features_based_on_transaction_interval = self.get_generated_features_based_on_transaction_interval(
+                    current_time_stamp, time_stamps)
+
+                generated_features.extend(generated_features_based_on_chosen_feature)
+                generated_features.extend(generated_features_based_on_transaction_number)
+                generated_features.extend(generated_features_based_on_transaction_interval)
+                number_of_generated_features = len(generated_features)
+                for interval in self.intervals:
+                    for k in range(number_of_generated_features):
+                        transaction_feature_list.append(generated_features[k].get(interval))
+
+                label = transaction_labels[i][0]
+                transaction_feature_list.append(label)
+                transaction_feature_tuple = tuple(transaction_feature_list)
+                extended_dataset.append(transaction_feature_tuple)
+                result=np.array(extended_dataset)
+        return result
+
+    def get_feature_to_be_engineered_by_timestamp_before_given_time_stamp(self, data_set, given_time_stamp,
+                                                                          index_of_feature_to_be_engineered,
+                                                                          index_of_timestamp):
+        feature_by_timestamp = dict()
+        time_stamps = list()
+        for record in data_set:
+            current_time_stamp_value = record[index_of_timestamp]
+            feature_to_be_engineered_value = record[index_of_feature_to_be_engineered]
+            if current_time_stamp_value < given_time_stamp:
+                time_stamps.append(current_time_stamp_value)
+                feature_by_timestamp[current_time_stamp_value] = feature_to_be_engineered_value
+        return feature_by_timestamp, time_stamps
+
+    def get_generated_features_based_on_chosen_feature(self, current_timestamp, feature_value,
+                                                       feature_to_be_engineered_by_timestamp):
+
+        feature_values_by_retrospective_time = dict()
+        for interval in self.intervals:
+            feature_values = list()
+            for timestamp_in_julian_date, feature_value in feature_to_be_engineered_by_timestamp.items():
+                if timestamp_in_julian_date > current_timestamp - interval:
+                    feature_values.append(feature_value)
+            feature_values_by_retrospective_time[interval] = feature_values
+
+        average_feature_value_by_retrospective_time = dict()
+        deviation_feature_value_by_retrospective_time = dict()
+        median_feature_value_by_retrospective_time = dict()
+
+        for interval in self.intervals:
+            feature_values = feature_values_by_retrospective_time.get(interval)
+            try:
+                average_feature_value_by_retrospective_time[interval] = statistics.mean(
+                    feature_values) if feature_values else 0
+                median_feature_value_by_retrospective_time[interval] = statistics.median(
+                    feature_values) if feature_values else 0
+                deviation_feature_value_by_retrospective_time[interval] = statistics.stdev(feature_values) if len(
+                    feature_values) >= 2 else 0
+            except TypeError as e:
+                self.logger.error("error")
+
+        feature_per_average_feature_value_by_retrospective_time = dict()
+        feature_minus_average_feature_value_by_retrospective_time = dict()
+        feature_per_median_feature_value_by_retrospective_time = dict()
+        feature_minus_median_feature_value_by_retrospective_time = dict()
+        feature_minus_average_feature_per_deviation_feature_value_by_retrospective_time = dict()
+        feature_minus_average_feature_minus_deviation_feature_value_by_retrospective_time = dict()
+
+        for interval in self.intervals:
+            feature_per_average_feature_value_by_retrospective_time[
+                interval] = feature_value / average_feature_value_by_retrospective_time.get(
+                interval) if average_feature_value_by_retrospective_time.get(interval) != 0 else 0
+            feature_minus_average_feature_value_by_retrospective_time[
+                interval] = feature_value - average_feature_value_by_retrospective_time.get(interval)
+            feature_per_median_feature_value_by_retrospective_time[
+                interval] = feature_value / median_feature_value_by_retrospective_time.get(
+                interval) if median_feature_value_by_retrospective_time.get(interval) != 0 else 0
+            feature_minus_median_feature_value_by_retrospective_time[
+                interval] = feature_value - median_feature_value_by_retrospective_time.get(interval)
+            feature_minus_average_feature_per_deviation_feature_value_by_retrospective_time[
+                interval] = feature_value / deviation_feature_value_by_retrospective_time.get(
+                interval) if deviation_feature_value_by_retrospective_time.get(interval) != 0 else 0
+            feature_minus_average_feature_minus_deviation_feature_value_by_retrospective_time[
+                interval] = feature_value - deviation_feature_value_by_retrospective_time.get(interval)
+        return [feature_per_average_feature_value_by_retrospective_time,
+                feature_minus_average_feature_value_by_retrospective_time,
+                feature_per_median_feature_value_by_retrospective_time,
+                feature_minus_median_feature_value_by_retrospective_time,
+                feature_minus_average_feature_per_deviation_feature_value_by_retrospective_time,
+                feature_minus_average_feature_minus_deviation_feature_value_by_retrospective_time]
+
+    def get_feature_values_by_julian_date(self, feature_and_timestamp_values):
+        feature_values_by_date_dictionary = dict()
+        for current_feature_and_timestamp in feature_and_timestamp_values:
+            current_feature = current_feature_and_timestamp[0]
+            timestamp = current_feature_and_timestamp[1]
+            current_julian_date = int(timestamp)
+            if feature_values_by_date_dictionary.get(current_julian_date) is None:
+                feature_collection = list()
+                feature_collection.append(current_feature)
+                feature_values_by_date_dictionary[current_julian_date] = feature_collection
+            else:
+                feature_collection = feature_values_by_date_dictionary.get(current_julian_date)
+                feature_collection.append(current_feature)
+        return feature_values_by_date_dictionary
+
+    def get_generated_features_based_on_transaction_number(self, current_time_stamp,
+                                                           time_stamps):
+
+        transaction_number_on_current_day = 1
+        for time_stamp_in_julian_format in time_stamps:
+            if time_stamp_in_julian_format > current_time_stamp - 1:
+                transaction_number_on_current_day += 1
+
+        transaction_number_by_interval = dict()
+        for interval in self.intervals:
+            transaction_number = 0
+            transaction_number_by_day_order_number = dict()
+            for i in range(interval):
+                transaction_number = 0
+                upper_boundara = current_time_stamp - i
+                lower_boundary = current_time_stamp - (i + 1)
+                for time_stamp_in_julian_format in time_stamps:
+                    if time_stamp_in_julian_format < upper_boundara and time_stamp_in_julian_format > lower_boundary:
+                        transaction_number += 1
+                transaction_number_by_day_order_number[i] = transaction_number
+            transactions_on_days = list()
+            for transaction_number_on_given_day in transaction_number_by_day_order_number.values():
+                transactions_on_days.append(transaction_number_on_given_day)
+            transaction_number_by_interval[interval] = transactions_on_days
+
+        daily_average_transaction_number_by_retrospective_time = dict()
+        daily_median_transaction_number_by_retrospective_time = dict()
+
+        for interval in self.intervals:
+            transaction_number = transaction_number_by_interval.get(interval)
+            daily_average_transaction_number_by_retrospective_time[interval] = statistics.mean(
+                transaction_number) if transaction_number else 0
+            daily_median_transaction_number_by_retrospective_time[interval] = statistics.median(
+                transaction_number) if transaction_number else 0
+
+        transaction_number_on_current_day_per_daily_average_transaction_number_by_retrospective_time = dict()
+        transaction_number_on_current_day_minus_daily_average_transaction_number_by_retrospective_time = dict()
+        transaction_number_on_current_day_per_daily_median_transaction_number_by_retrospective_time = dict()
+        transaction_number_on_current_day_minus_daily_median_transaction_number_by_retrospective_time = dict()
+        for interval in self.intervals:
+            average_transaction_number = daily_average_transaction_number_by_retrospective_time.get(interval)
+            median_transaction_number = daily_median_transaction_number_by_retrospective_time.get(interval)
+            transaction_number_on_current_day_per_daily_average_transaction_number_by_retrospective_time[
+                interval] = transaction_number_on_current_day / average_transaction_number if average_transaction_number != 0 else 0
+            transaction_number_on_current_day_minus_daily_average_transaction_number_by_retrospective_time[
+                interval] = transaction_number_on_current_day - average_transaction_number
+            transaction_number_on_current_day_per_daily_median_transaction_number_by_retrospective_time[
+                interval] = transaction_number_on_current_day / median_transaction_number if median_transaction_number != 0 else 0
+            transaction_number_on_current_day_minus_daily_median_transaction_number_by_retrospective_time[
+                interval] = transaction_number_on_current_day - median_transaction_number
+
+        return [transaction_number_on_current_day_per_daily_average_transaction_number_by_retrospective_time,
+                transaction_number_on_current_day_minus_daily_average_transaction_number_by_retrospective_time,
+                transaction_number_on_current_day_per_daily_median_transaction_number_by_retrospective_time,
+                transaction_number_on_current_day_minus_daily_median_transaction_number_by_retrospective_time]
+
+    def get_generated_features_based_on_transaction_interval(self, current_timestamp,
+                                                             time_stamps):
+        current_distance_between_transactions = current_timestamp - time_stamps[0] if time_stamps else 0
+
+        distances_by_retrospective_time = dict()
+        length_of_timestamp_collection = len(time_stamps)
+        for interval in self.intervals:
+            distances = list()
+            for i in range(0, length_of_timestamp_collection - 1, 1):
+                next_time_stamp = time_stamps[i + 1]
+                current_time_stamp = time_stamps[i]
+                distance_between_transactions = current_time_stamp - next_time_stamp
+                distances.append(distance_between_transactions)
+            distances_by_retrospective_time[interval] = distances
+
+        average_distance_between_transactions_by_retrospective_time = dict()
+        median_distance_between_transactions_by_retrospective_time = dict()
+        for interval in self.intervals:
+            distances = distances_by_retrospective_time.get(interval)
+            average_distance_between_transactions_by_retrospective_time[interval] = statistics.mean(
+                distances) if distances else 0
+            median_distance_between_transactions_by_retrospective_time[interval] = statistics.median(
+                distances) if distances else 0
+
+        average_distance_per_current_distance_between_transactions_by_retrospective_time = dict()
+        average_distance_time_minus_current_distance_between_transactions_by_retrospective_time = dict()
+        median_distance_per_current_distance_between_transactions_by_retrospective_time = dict()
+        median_distance_minus_current_distance_between_transactions_by_retrospective_time = dict()
+
+        for interval in self.intervals:
+            average_distance = average_distance_between_transactions_by_retrospective_time.get(interval)
+            median_distance = median_distance_between_transactions_by_retrospective_time.get(interval)
+            average_distance_per_current_distance_between_transactions_by_retrospective_time[
+                interval] = average_distance / current_distance_between_transactions if current_distance_between_transactions != 0 else 0
+            average_distance_time_minus_current_distance_between_transactions_by_retrospective_time[
+                interval] = average_distance - current_distance_between_transactions
+            median_distance_per_current_distance_between_transactions_by_retrospective_time[
+                interval] = median_distance / current_distance_between_transactions if current_distance_between_transactions != 0 else 0
+            median_distance_minus_current_distance_between_transactions_by_retrospective_time[
+                interval] = median_distance - current_distance_between_transactions
+
+        return [average_distance_per_current_distance_between_transactions_by_retrospective_time,
+                average_distance_time_minus_current_distance_between_transactions_by_retrospective_time,
+                median_distance_per_current_distance_between_transactions_by_retrospective_time,
+                median_distance_minus_current_distance_between_transactions_by_retrospective_time]
diff --git a/Train/src/error_module.py b/Train/src/error_module.py
new file mode 100644 (file)
index 0000000..519e2c3
--- /dev/null
@@ -0,0 +1,7 @@
+from enum import Enum, unique
+
+
+@unique
+class Error(Enum):
+    ddl_build_error = 1
+    fit_launch_condition_error = 2
diff --git a/Train/src/exception_module.py b/Train/src/exception_module.py
new file mode 100644 (file)
index 0000000..e1b73be
--- /dev/null
@@ -0,0 +1,17 @@
+import error_module
+
+
+class CommonCustomException(Exception):
+    def __init__(self, error_message):
+        self.error_message = error_message
+
+
+class DdlBuildException(CommonCustomException):
+    def __init__(self, error_message):
+        CommonCustomException.__init__(self, error_message)
+        self.error = error_module.Error.ddl_build_error
+
+class FitLaunchConditionException(CommonCustomException):
+    def __init__(self, error_message):
+        CommonCustomException.__init__(self, error_message)
+        self.error = error_module.Error.fit_launch_condition_error
\ No newline at end of file
diff --git a/Train/src/requirements.txt b/Train/src/requirements.txt
new file mode 100644 (file)
index 0000000..642e123
--- /dev/null
@@ -0,0 +1,27 @@
+click==8.0.3
+colorama==0.4.4
+Flask==2.0.2
+imbalanced-learn==0.9.0
+imblearn==0.0
+importlib-metadata==4.10.1
+itsdangerous==2.0.1
+Jinja2==3.0.3
+joblib==1.1.0
+lightgbm==3.3.2
+MarkupSafe==2.0.1
+mysql-connector==2.2.9
+mysql-connector-python==8.0.28
+numpy==1.21.5
+pandas==1.3.5
+pika==1.2.0
+protobuf==3.19.3
+python-dateutil==2.8.2
+pytz==2021.3
+scikit-learn==1.0.2
+scipy==1.7.3
+six==1.16.0
+threadpoolctl==3.0.0
+typing-extensions==4.0.1
+Werkzeug==2.0.2
+xgboost==1.5.2
+zipp==3.7.0
diff --git a/Train/src/server.py b/Train/src/server.py
new file mode 100644 (file)
index 0000000..832a6ce
--- /dev/null
@@ -0,0 +1,1350 @@
+import csv
+import json
+import logging
+import random
+
+import math
+import os
+import time
+from threading import Thread
+
+import pika
+from flask import Flask, jsonify, request, Response
+from imblearn.combine import SMOTEENN
+from imblearn.over_sampling import RandomOverSampler
+from imblearn.under_sampling import RandomUnderSampler
+from lightgbm import LGBMClassifier
+from sklearn.decomposition import PCA, TruncatedSVD
+from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
+from sklearn.ensemble import RandomForestClassifier
+from sklearn.feature_selection import RFECV
+from sklearn.linear_model import LogisticRegression
+from sklearn.metrics import confusion_matrix, accuracy_score, balanced_accuracy_score, recall_score, f1_score, \
+    roc_auc_score
+from sklearn.model_selection import train_test_split
+from sklearn.naive_bayes import GaussianNB
+from sklearn.neighbors import KNeighborsClassifier
+from sklearn.pipeline import Pipeline
+from sklearn.preprocessing import StandardScaler, QuantileTransformer, Normalizer, RobustScaler, MaxAbsScaler, \
+    MinMaxScaler
+from sklearn.tree import DecisionTreeClassifier
+from sklearn.utils.validation import column_or_1d
+from xgboost import XGBClassifier
+
+import database_module
+import exception_module
+import error_module
+
+DEFAULT_INSERT_BATCH_SIZE = 1000
+PREVIOUSLY_DONE = "PREVIOUSLY_DONE"
+STARTED = "STARTED"
+FINISHED = "FINISHED"
+ENCODING = "ENCODING"
+FEATURE_ENGINEERING = "FEATURE_ENGINEERING"
+SAMPLING = "SAMPLING"
+FITTING = "FITTING"
+ESTIMATOR_PERSISTING = "ESTIMATOR_PERSISTING"
+METRIC_PERSISTING = "METRIC_PERSISTING"
+IN_PROGRESS = "IN_PROGRESS"
+DONE = "DONE"
+FRAUD_TYPE = "fraud_type"
+
+app = Flask(__name__)
+
+
+class IllegalArgumentException(Exception):
+    pass
+
+
+class NoTimeBaseFieldInTableException(Exception):
+    pass
+
+
+class FieldNotApplicableForTimeBaseException(Exception):
+    pass
+
+
+class NoFraudTypeFieldException(Exception):
+    pass
+
+
+class TooManyFraudTypeFieldException(Exception):
+    pass
+
+
+class NoEnoughParameterException(Exception):
+    pass
+
+
+class FieldNotExistInTableException(Exception):
+    pass
+
+
+class EncodingNotApplicableException(Exception):
+    pass
+
+
+class FeatureEngineeringNotApplicableException(Exception):
+    pass
+
+
+class FieldNotApplicableForFraudException(Exception):
+    pass
+
+
+def send_async_message(id, method, status):
+    response = {
+        "train_task_id": id,
+        "method": method,
+        "status": status
+    }
+    try:
+        connection = pika.BlockingConnection(
+            pika.ConnectionParameters(host=rabbitmq_url, port=5672))
+        channel = connection.channel()
+        channel.queue_declare(queue='train', durable=True)
+        channel.basic_publish(exchange='', routing_key='train', body=json.dumps(response))
+        connection.close()
+    except (pika.exceptions.AMQPConnectionError, pika.exceptions.AMQPChannelError) as error:
+        logger.error(f"RabbitMQ error: {error}")
+
+
+def get_feature_selectors(cpu_core_num=1):
+    feature_selectors = {
+        1: ('RFE', RFECV(estimator=RandomForestClassifier(n_jobs=cpu_core_num), n_jobs=cpu_core_num)),
+        2: ('PCA', PCA(n_components=0.95, svd_solver='full')),
+        3: ('SVD', TruncatedSVD())
+    }
+    return feature_selectors
+
+
+def get_samplers(cpu_core_num=1):
+    samplers = {
+        1: ('RandomUnderSampler', RandomUnderSampler(sampling_strategy=0.5)),
+        2: ('RandomOverSampler', RandomOverSampler(sampling_strategy=0.5)),
+        3: ('SMOTEENN', SMOTEENN(sampling_strategy=0.5, n_jobs=cpu_core_num))
+    }
+    return samplers
+
+
+def get_scalers():
+    scalers = {
+        1: ('StandardScaler', StandardScaler()),
+        2: ('MinMaxScaler', MinMaxScaler()),
+        3: ('MaxAbsScaler', MaxAbsScaler()),
+        4: ('RobustScaler', RobustScaler()),
+        5: ('QuantileTransformer-Normal', QuantileTransformer(output_distribution='normal')),
+        6: ('QuantileTransformer-Uniform', QuantileTransformer(output_distribution='uniform')),
+        7: ('Normalizer', Normalizer()),
+    }
+    return scalers
+
+
+def get_models(cpu_core_num=1):
+    models = {
+        1: ('Logistic Regression', LogisticRegression(n_jobs=cpu_core_num)),
+        2: ('LinearDiscriminantAnalysis', LinearDiscriminantAnalysis()),
+        3: ('K-Nearest Neighbor', KNeighborsClassifier(n_jobs=cpu_core_num)),
+        4: ('DecisionTree', DecisionTreeClassifier()),
+        5: ('GaussianNB', GaussianNB()),
+        # 'SupportVectorMachine GPU': SupportVectorMachine(use_gpu=True),
+        # 'Random Forest GPU': RandomForestClassifier(use_gpu=True, gpu_ids=[0, 1], use_histograms=True),
+        6: ('Random Forest', RandomForestClassifier(n_jobs=cpu_core_num)),
+        # 'MLP': MLPClassifier(),
+        7: ('Light GBM', LGBMClassifier(n_jobs=cpu_core_num)),
+        # 'XGBoost': XGBClassifier(tree_method='gpu_hist', gpu_id=0)
+        8: ('XGBoost', XGBClassifier())
+    }
+    return models
+
+
+@app.route('/liveness', methods=['GET'])
+def get_liveness():
+    result = {
+        "response": "OK"
+    }
+    return jsonify(result)
+
+
+@app.route('/cpu', methods=['GET'])
+def get_available_cpu_core():
+    core_number = os.cpu_count()
+    logger.debug(f"available cpu core number: {core_number}")
+    return jsonify(core_number)
+
+
+@app.route('/upload', methods=['POST'])
+def upload_train_database_from_csv():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    request_parameter = request.get_json()
+    if request_parameter is None or not request_parameter:
+        error_message = "train database upload request's POST body is empty"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    try:
+        database_handler.upload_train_database(request_parameter)
+    except exception_module.DdlBuildException as e:
+        return create_error_response(e)
+        # details = e.args[0]
+        # error_message = details.get("error_message")
+        # error = e.error
+        # error_obj = {
+        #     "error_name": e.error.name,
+        #     "error_code": e.error.value,
+        #     "error_message": e.error_message
+        # }
+        # return jsonify(error_obj),400
+    except database_module.IllegalArgumentException as e:
+        details = e.args[0]
+        message = details.get("message")
+        parameter_name = details.get("parameter_name")
+        value = details.get("value")
+        id = details.get("id")
+        error_message = f"{message}" + (
+            f", parameter name: {parameter_name}" if parameter_name is not None else "") + (
+                            f", value: {value}" if value is not None else "") + (
+                            f", id: {id}" if id is not None else "")
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    message = "Train database uploaded"
+    return Response(message, status=201)
+
+
+def create_error_response(common_exception):
+    error_obj = {
+        "error_name": common_exception.error.name,
+        "error_code": common_exception.error.value,
+        "error_message": common_exception.error_message
+    }
+    return jsonify(error_obj), 400
+
+
+@app.route('/schema', methods=['GET'])
+def get_schemas():
+    schemas = database_handler.get_schema_names()
+    return jsonify(schemas)
+
+
+@app.route('/table', methods=['GET'])
+def get_tables():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    tables = database_handler.get_table_names_of_given_database(schema_name)
+    return jsonify(tables)
+
+
+@app.route('/field_name_in_ordinal_position', methods=['GET'])
+def get_field_name_in_ordinal_position():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "Schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    table_name = request.args.get("table_name")
+    if table_name is None:
+        message = "table name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    field_name_in_ordinal_position = database_handler.get_field_name_in_ordinal_position(schema_name, table_name)
+    return jsonify(field_name_in_ordinal_position)
+
+
+@app.route('/field_type_in_ordinal_position', methods=['GET'])
+def get_field_type_in_ordinal_position():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "Schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    table_name = request.args.get("table_name")
+    if table_name is None:
+        message = "table name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    field_name_in_ordinal_position = database_handler.get_field_type_in_ordinal_position(schema_name, table_name)
+    return jsonify(field_name_in_ordinal_position)
+
+
+@app.route('/field_name_and_type', methods=['GET'])
+def get_field_type():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "Schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    table_name = request.args.get("table_name")
+    if table_name is None:
+        message = "table name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    field_types_by_name = database_handler.get_field_types_by_name(schema_name, table_name)
+    return jsonify(field_types_by_name)
+
+
+@app.route('/record', methods=['GET'])
+def get_records_with_limit_and_offset():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "Schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    table_name = request.args.get("table_name")
+    if table_name is None:
+        message = "table name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    limit = request.args.get("limit")
+    if limit is not None and type(limit) != int:
+        try:
+            limit = int(limit)
+        except ValueError:
+            message = "limit isn't integer"
+            logger.error(message)
+            return Response(message, 400)
+    offset = request.args.get("offset")
+    if offset is not None and type(offset) != int:
+        try:
+            offset = int(offset)
+        except ValueError:
+            message = "offset isn't integer"
+            logger.error(message)
+            return Response(message, 400)
+    result = database_handler.get_records_with_limit_and_offset(schema_name, table_name, limit, offset)
+    return jsonify(result)
+
+
+@app.route('/table_info', methods=['GET'])
+def get_table_info():
+    schema_name = request.args.get("schema_name")
+    table_name = request.args.get("table_name")
+    try:
+        check_schema(schema_name)
+        check_table(schema_name, table_name)
+    except IllegalArgumentException as e:
+        details = e.args[0]
+        message = details.get("message") + (
+            f", schema name: {schema_name}" if schema_name is not None and schema_name != "" else "") + (
+                      f", table name: {table_name}" if table_name is not None and table_name != "" else "")
+        return Response(message, 400)
+    detailed_information_about_table = database_handler.get_detailed_information_about_table(schema_name=schema_name,
+                                                                                             table_name=table_name)
+    return jsonify(detailed_information_about_table)
+
+
+def check_schema(schema_name):
+    if schema_name is None or schema_name == "":
+        message = "schema name is missing"
+        logger.error(message)
+        raise IllegalArgumentException(({"message": message}))
+    schemas = database_handler.get_schema_names()
+    if schema_name not in schemas:
+        message = "schema doesn't exist"
+        logger.error(message)
+        raise IllegalArgumentException({"message": message})
+
+
+def check_table(schema_name, table_name):
+    tables = database_handler.get_table_names_of_given_database(schema_name)
+    if table_name is None or table_name == "":
+        message = "table name is missing"
+        logger.error(message)
+        raise IllegalArgumentException({"message": message})
+    if table_name not in tables:
+        message = "table doesn't exist"
+        logger.error(message)
+        raise IllegalArgumentException({"message": message})
+
+
+@app.route('/encoding_and_feature_engineering_plan', methods=['POST'])
+def save_encoding_plan():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    request_parameter = request.get_json()
+    if request_parameter is None or not request_parameter:
+        error_message = "encoding plan request's POST body is empty"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    schema_name = request_parameter.get("schema_name")
+    if schema_name is None:
+        error_message = "encoding plan request's schema name is missing"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    schemas = database_handler.get_schema_names()
+    if schema_name not in schemas:
+        error_message = "encoding plan request's schema name doesn't exist in database"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    table_name = request_parameter.get("table_name")
+    if table_name is None:
+        error_message = "encoding plan request's table name is missing"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    tables = database_handler.get_table_names_of_given_database(schema_name)
+    if table_name not in tables:
+        error_message = "encoding plan request's table name doesn't exist in the database"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    time_base_field_name = request_parameter.get("time_base_field_name")
+    if time_base_field_name is None or not time_base_field_name:
+        error_message = "encoding plan request's time base field name is missing"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    encoding_parameters = request_parameter.get("encoding_parameters")
+    if encoding_parameters is None or not encoding_parameters:
+        error_message = "encoding plan request's encoding parameters are missing"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    feature_engineering_parameters = request_parameter.get("feature_engineering_parameters")
+    if feature_engineering_parameters is None or not feature_engineering_parameters:
+        logger.info("feature engineering didn't set")
+
+    detailed_information_about_table = database_handler.get_detailed_information_about_table(schema_name=schema_name,
+                                                                                             table_name=table_name)
+
+    try:
+        check_time_base_field_name(detailed_information_about_table, time_base_field_name)
+        check_if_plan_exist_yet(schema_name, table_name, time_base_field_name, encoding_parameters,
+                                feature_engineering_parameters)
+        check_fields_and_possible_encoding_and_engineering(detailed_information_about_table,
+                                                           encoding_parameters, feature_engineering_parameters)
+
+    except NoTimeBaseFieldInTableException as e:
+        details = e.args[0]
+        message = details.get("message")
+        logger.error(message)
+        return Response(message, status=400)
+    except FieldNotApplicableForTimeBaseException as e:
+        details = e.args[0]
+        message = details.get("message")
+        field_name = details.get("field_name")
+        error_message = f"{message}, field name: {field_name}"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except IllegalArgumentException as e:
+        details = e.args[0]
+        message = details.get("message")
+        id = details.get("id")
+        info_message = f"{message}, id: {id}"
+        logger.info(info_message)
+        return jsonify(id)
+    except NoEnoughParameterException as e:
+        details = e.args[0]
+        message = details.get("message")
+        parameter_name = details.get("parameter_name")
+        error_message = f"{message}, parameter: {parameter_name}"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except FieldNotExistInTableException as e:
+        details = e.args[0]
+        message = details.get("message")
+        field_name = details.get("field_name")
+        error_message = f"{message}, schema name: {schema_name}, table name: {table_name}, field name: {field_name}"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except FieldNotApplicableForFraudException as e:
+        details = e.args[0]
+        message = details.get("message")
+        field_name = details.get("field_name")
+        error_message = f"{message}, schema name: {schema_name}, table name: {table_name}, field name: {field_name}"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except EncodingNotApplicableException as e:
+        details = e.args[0]
+        message = details.get("message")
+        field_name = details.get("field_name")
+        planned_encoding_type = details.get("planned_encoding_type")
+        error_message = f"{message}, schema name: {schema_name}, table name: {table_name}, field name: {field_name}, planned encoding type: {planned_encoding_type}"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except NoFraudTypeFieldException as e:
+        error_message = "There aren't any fraud type field in encoding parameters"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except TooManyFraudTypeFieldException as e:
+        error_message = "There are too many fraud type field in encoding parameters"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except FeatureEngineeringNotApplicableException as e:
+        details = e.args[0]
+        message = details.get("message")
+        field_name = details.get("field_name")
+        field_type = details.get("field_type")
+        error_message = f"{message}" + (f", field name: {field_name}" if field_type is not None else "") + (
+            f", field type: {field_type}" if field_type is not None else "")
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    last_row_id = database_handler.persist_encoding_and_engineering_plan(schema_name, table_name,
+                                                                         detailed_information_about_table,
+                                                                         time_base_field_name,
+                                                                         encoding_parameters,
+                                                                         feature_engineering_parameters)
+    return jsonify(last_row_id)
+
+
+@app.route('/encoding_and_feature_engineering_plan', methods=['GET'])
+def get_encoded_table():
+    id = request.args.get("id")
+    try:
+        if id is None or id == "":
+            result = database_handler.get_all_encoding_and_feature_engineering_plan()
+        else:
+            result = database_handler.get_encoding_and_feature_engineering_plan_by_id(id)
+    except database_module.NoneException as e:
+        details = e.args[0]
+        message = details.get("message")
+        parameter = details.get("parameter")
+        error_message = f"{message}, id: {parameter}"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    return jsonify(result)
+
+
+@app.route('/available_feature_selector', methods=['GET'])
+def get_available_feature_selector():
+    result = dict()
+    feature_selectors = get_feature_selectors()
+    for key, value in feature_selectors.items():
+        result[key] = value[0]
+    return jsonify(result)
+
+
+@app.route('/available_sampler', methods=['GET'])
+def get_available_sampler():
+    result = dict()
+    samplers = get_samplers()
+    for key, value in samplers.items():
+        result[key] = value[0]
+    return jsonify(result)
+
+
+@app.route('/available_scaler', methods=['GET'])
+def get_available_scaler():
+    result = dict()
+    scalers = get_scalers()
+    for key, value in scalers.items():
+        result[key] = value[0]
+    return jsonify(result)
+
+
+@app.route('/available_model', methods=['GET'])
+def get_available_model():
+    result = dict()
+    models = get_models()
+    for key, value in models.items():
+        result[key] = value[0]
+    return jsonify(result)
+
+
+@app.route('/train_task', methods=['POST'])
+def save_train_task():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    feature_selector_keys = get_feature_selectors().keys()
+    sampler_keys = get_samplers().keys()
+    scaler_keys = get_scalers().keys()
+    model_keys = get_models().keys()
+    request_parameter = request.get_json()
+    if request_parameter is None:
+        error_message = "Train task POST body is empty"
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    try:
+        result = database_handler.persist_train_task(request_parameter, feature_selector_keys, sampler_keys,
+                                                     scaler_keys, model_keys)
+    except database_module.IllegalArgumentException as e:
+        details = e.args[0]
+        message = details.get("message")
+        parameter_name = details.get("parameter_name")
+        value = details.get("value")
+        id = details.get("id")
+        error_message = f"{message}" + (
+            f", parameter name: {parameter_name}" if parameter_name is not None else "") + (
+                            f", value: {value}" if value is not None else "") + (
+                            f", id: {id}" if id is not None else "")
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    except database_module.TrainTaskExistYetException as e:
+        details = e.args[0]
+        message = details.get("message")
+        id = details.get("id")
+        info_message = f"{message}, id: {id}"
+        logger.info(info_message)
+        return jsonify(id)
+    return jsonify(result)
+
+
+@app.route('/train_task', methods=['GET'])
+def get_train_task():
+    id = request.args.get("id")
+    if id is None:
+        result = database_handler.get_all_train_task()
+    else:
+        result = database_handler.get_train_task_by_id(id)
+    return jsonify(result)
+
+
+@app.route('/fit', methods=['POST'])
+def fit():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+
+    request_parameter = request.get_json()
+    train_task_id = request_parameter.get("train_task_id")
+    used_cpu_core = request_parameter.get("used_cpu_core")
+    available_cpu_core = os.cpu_count()
+    if used_cpu_core is None:
+        message = f"used cpu core number not set, use the whole available logical cpu core, core number: {available_cpu_core}"
+        logger.info(message)
+        used_cpu_core = available_cpu_core
+    else:
+        if used_cpu_core > available_cpu_core or used_cpu_core < 1:
+            message = f"To many or few cpu core has been set, use the whole available logical cpu core, core number: {available_cpu_core}"
+            logger.info(message)
+            used_cpu_core = available_cpu_core
+
+    train_task_status = database_handler.get_train_task_status(train_task_id)
+    try:
+        if train_task_status == IN_PROGRESS:
+            error_message = f"this train task is in progress yet, it can not be launched once more, train_task_id: {train_task_id}"
+            logger.error(error_message)
+            raise exception_module.FitLaunchConditionException(error_message)
+        if train_task_status == DONE:
+            error_message = f"this train task has been finished yet, train_task_id: {train_task_id}"
+            logger.error(error_message)
+            raise exception_module.FitLaunchConditionException(error_message)
+        is_any_train_task_in_progress = database_handler.check_if_any_train_in_progress()
+        if is_any_train_task_in_progress:
+            error_message = "train in progress yet, it can not be launched another train task"
+            logger.error(error_message)
+            raise exception_module.FitLaunchConditionException(error_message)
+    except exception_module.FitLaunchConditionException as e:
+        return create_error_response(e)
+
+    database_handler.set_train_task_status(train_task_id, IN_PROGRESS)
+
+    available_feature_selectors = get_feature_selectors(used_cpu_core)
+    available_samplers = get_samplers(used_cpu_core)
+    available_scalers = get_scalers()
+    available_models = get_models(used_cpu_core)
+    try:
+        if train_task_id is None:
+            message = "train_task_id not set"
+            raise IllegalArgumentException({"message": message})
+
+        train_parameters = database_handler.get_train_parameters(train_task_id)
+        if train_parameters is None:
+            message = "No train task belongs to this id"
+            logger.error(message)
+            raise IllegalArgumentException({"message": message})
+        transaction_identifier_field_name = train_parameters[1]
+        if transaction_identifier_field_name is None:
+            message = "Transaction identifier field name not set"
+            raise IllegalArgumentException({"message": message})
+
+        planned_encoding_and_feature_engineering_id = train_parameters[3]
+        if planned_encoding_and_feature_engineering_id is None:
+            message = "Planned encoding and feature engineering id not set"
+            raise IllegalArgumentException({"message": message})
+
+        feature_selector_key = train_parameters[4]
+        if feature_selector_key is None:
+            message = "Feature selector not set"
+            raise IllegalArgumentException({"message": message})
+
+        feature_selector_name = available_feature_selectors.get(int(feature_selector_key))[0]
+        feature_selector = available_feature_selectors.get(int(feature_selector_key))[1]
+        logger.info(f"Feature selector: {feature_selector_name}")
+        if feature_selector is None:
+            message = "Feature selector doesn't exist in train application"
+            raise IllegalArgumentException({"message": message})
+
+        sampler_key = train_parameters[5]
+        if sampler_key is None:
+            message = "Sampler not set"
+            raise IllegalArgumentException({"message": message})
+        sampler_name = available_samplers.get(int(sampler_key))[0]
+        sampler = available_samplers.get(int(sampler_key))[1]
+        logger.info(f"Sampler: {sampler_name}")
+        if sampler is None:
+            message = "Sampler doesn't exist in train application"
+            raise IllegalArgumentException({"message": message})
+
+        scaler_key = train_parameters[6]
+        if scaler_key is None:
+            message = "Scaler not set"
+            raise IllegalArgumentException({"message": message})
+        scaler_name = available_scalers.get(int(scaler_key))[0]
+        scaler = available_scalers.get(int(scaler_key))[1]
+        logger.info(f"Scaler: {scaler_name}")
+        if scaler is None:
+            message = "Scaler doesn't exist in train application"
+            raise IllegalArgumentException({"message": message})
+
+        model_key = train_parameters[7]
+        if model_key is None:
+            message = "Model not set"
+            raise IllegalArgumentException({"message": message})
+        model_name = available_models.get(int(model_key))[0]
+        model = available_models.get(int(model_key))[1]
+        logger.info(f"Model: {model_name}")
+        if model is None:
+            message = "Model doesn't exist in train application"
+            raise IllegalArgumentException({"message": message})
+
+        split_test_size = None
+        saved_test_split_size = train_parameters[8]
+        if saved_test_split_size is None:
+            logger.info("No test split size set, use default value, 0.25")
+        else:
+            split_test_size = saved_test_split_size
+            logger.info(f"Train test split size: {split_test_size}")
+
+        fit_process_parameter = dict()
+        fit_process_parameter["transaction_identifier_field_name"] = transaction_identifier_field_name
+        fit_process_parameter["train_task_id"] = train_task_id
+        fit_process_parameter[
+            "planned_encoding_and_feature_engineering_id"] = planned_encoding_and_feature_engineering_id
+        fit_process_parameter["feature_selector"] = feature_selector
+        fit_process_parameter["sampler"] = sampler
+        fit_process_parameter["scaler"] = scaler
+        fit_process_parameter["model"] = model
+        fit_process_parameter["used_cpu_core"] = used_cpu_core
+        fit_process_parameter["split_test_size"] = split_test_size
+        thread = Thread(target=process_fit, args=(fit_process_parameter,))
+        thread.start()
+        logger.info(f"Train parameters are proper, fit process started, train_task_id: {train_task_id}")
+        response = Response(status=200)
+    except IllegalArgumentException as e:
+        details = e.args[0]
+        message = details.get("message")
+        logger.error(message)
+        request_message = message + (
+            f", train_task_id: {train_task_id}" if train_task_id is not None else "")
+        response = Response(request_message, status=400)
+    return response
+
+
+@app.route('/metrics', methods=['GET'])
+def get_metrics():
+    estimator_id = request.args.get("id")
+    if estimator_id is None:
+        result = database_handler.get_all_metrics()
+    else:
+        result = database_handler.get_metrics_by_id(estimator_id)
+    return jsonify(result)
+
+
+@app.route('/estimator', methods=['GET'])
+def get_estimators():
+    train_task_id = request.args.get("train_task_id")
+    if train_task_id is None:
+        result = database_handler.get_all_estimator()
+    else:
+        result = database_handler.get_estimator_by_train_task_id(train_task_id)
+    return jsonify(result)
+
+
+@app.route('/transaction_identifier_field_name', methods=['GET'])
+def get_transaction_identifier_field_name():
+    id = request.args.get("id")
+    if id is None:
+        message = "id is missing"
+        logger.error(message)
+        return Response(message, status=400)
+    field_name = database_handler.get_transaction_identifier_field_name_by_estimator_id(id)
+    return field_name
+
+
+@app.route('/confusion_matrix', methods=['GET'])
+def get_confusion_matrix():
+    train_task_id = request.args.get("train_task_id")
+    if train_task_id is None:
+        message = "Train task id is missing"
+        logger.error(message)
+        return Response(message, status=400)
+    else:
+        result = database_handler.get_confusion_matrix_elements_by_train_task_id(train_task_id)
+    return jsonify(result)
+
+
+@app.route('/train_task_csv', methods=['POST'])
+def create_train_task_csv():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    request_parameter = request.get_json()
+    path = request_parameter.get("path")
+    file_name = request_parameter.get("file_name")
+    date_time_to_append = request_parameter.get("date_time_to_append")
+    available_feature_selectors = get_feature_selectors()
+    available_samplers = get_samplers()
+    available_scalers = get_scalers()
+    available_models = get_models()
+    try:
+        database_handler.create_train_task_csv_file(path, file_name, date_time_to_append, available_feature_selectors,
+                                                    available_samplers,
+                                                    available_scalers, available_models)
+    except IllegalArgumentException as e:
+        details = e.args[0]
+        message = details.get("message")
+        logger.error(message)
+        return Response(message, status=400)
+    return Response(status=200)
+
+
+@app.route('/train_task_csv_content', methods=['GET'])
+def get_train_task_csv_content():
+    available_feature_selectors = get_feature_selectors()
+    available_samplers = get_samplers()
+    available_scalers = get_scalers()
+    available_models = get_models()
+    header_elements, lines = database_handler.create_train_task_csv_content(available_feature_selectors,
+                                                                            available_samplers,
+                                                                            available_scalers, available_models)
+    response = {
+        "header_elements": header_elements,
+        "lines": lines
+    }
+    return jsonify(response)
+
+
+@app.route('/metircs_csv', methods=['POST'])
+def create_metrics_csv():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    request_parameter = request.get_json()
+    path = request_parameter.get("path")
+    file_name = request_parameter.get("file_name")
+    date_time_to_append = request_parameter.get("date_time_to_append")
+    try:
+        database_handler.create_metrics_csv_file(path, file_name, date_time_to_append)
+    except PermissionError as e:
+        message = f"Permission error occured writing csv file, path: {path}, file name: {file_name}"
+        logger.error(message)
+        return Response(message, status=400)
+    except csv.Error as e:
+        message = f"Csv error occured writing csv file, path: {path}, file name: {file_name}"
+        logger.error(message)
+        return Response(message, status=400)
+    except IOError as e:
+        message = f"IO error occured writing csv file, path: {path}, file name: {file_name}"
+        logger.error(message)
+        return Response(message, status=400)
+    return Response(status=200)
+
+
+@app.route('/metrics_csv_content', methods=['GET'])
+def get_metrics_csv_content():
+    header_elements, lines = database_handler.create_metrics_csv_content()
+    response = {
+        "header_elements": header_elements,
+        "lines": lines
+    }
+    return jsonify(response)
+
+
+@app.route('/train_task_name_by_estimator_id', methods=['GET'])
+def get_train_task_name_by_estimator_id():
+    estimator_id = request.args.get("estimator_id")
+    if estimator_id is None:
+        message = "Estimator id is missing"
+        logger.error(message)
+        return Response(message, status=400)
+    else:
+        result = database_handler.get_train_task_name_by_estimator_id(estimator_id)
+        if result is not None:
+            return result
+        else:
+            message = "Train task name didn't find by estimator id"
+            logger.error(message)
+            return Response(message, status=400)
+
+
+@app.route('/columns_contain_only_distinct_values', methods=['GET'])
+def get_columns_contain_only_distinct_values():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    table_name = request.args.get("table_name")
+    if table_name is None:
+        message = "table name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    response = database_handler.get_columns_contain_only_distinct_values(schema_name, table_name)
+    return jsonify(response)
+
+
+@app.route('/reduced_and_evaulation', methods=['POST'])
+def create_reduced_and_evaulated_tables():
+    if not request.is_json:
+        error_message = "request body must be JSON"
+        logger.error(error_message)
+        return Response(error_message, status=415)
+    request_parameter = request.get_json()
+    schema_name = request_parameter.get("schema_name")
+    table_name = request_parameter.get("table_name")
+    fraud_type_field_name = request_parameter.get("fraud_type_field_name")
+    positive_fraud_case_number = request_parameter.get("positive_fraud_case_number")
+    negative_fraud_case_number = request_parameter.get("negative_fraud_case_number")
+    date_suffix_to_append = request_parameter.get("date_suffix_to_append")
+    try:
+        csv_content_and_planned_file_name = database_handler.create_reduced_and_for_evaluation_tables(schema_name,
+                                                                                                      table_name,
+                                                                                                      fraud_type_field_name,
+                                                                                                      positive_fraud_case_number,
+                                                                                                      negative_fraud_case_number,
+                                                                                                      date_suffix_to_append)
+    except database_module.IllegalArgumentException as e:
+        details = e.args[0]
+        message = details.get("message")
+        parameter_name = details.get("parameter_name")
+        error_message = f"{message}" + (f", parameter name: {parameter_name}" if parameter_name is not None else "")
+        logger.error(error_message)
+        return Response(error_message, status=400)
+    return jsonify(csv_content_and_planned_file_name)
+
+
+@app.route('/generic_records', methods=['GET'])
+def get_generic_records_from_evaluation_table():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    table_name = request.args.get("table_name")
+    if table_name is None:
+        message = "table name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    response = database_handler.get_generic_records_for_evaluation(schema_name, table_name)
+    return jsonify(response)
+
+@app.route('/max_retrospective_day', methods=['GET'])
+def get_max_retrospective_day():
+    schema_name = request.args.get("schema_name")
+    if schema_name is None:
+        message = "schema name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    table_name = request.args.get("table_name")
+    if table_name is None:
+        message = "table name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    time_base_field_name= request.args.get("time_base_field_name")
+    if time_base_field_name is None:
+        message = "time_base_field_name is missing"
+        logger.error(message)
+        return Response(message, 400)
+    max_retrospective_day=database_handler.get_max_retrospective_day(schema_name,table_name,time_base_field_name)
+    return jsonify(max_retrospective_day)
+
+
+def check_time_base_field_name(detailed_information_about_table, time_base_field_name):
+    field_names = list()
+    time_base_field_names = list()
+    field_properties = detailed_information_about_table.get("fields")
+    for field_property in field_properties:
+        field_type = field_property.get("type")
+        field_name = field_property.get("name")
+        field_names.append(field_name)
+        if field_type == "datetime":
+            time_base_field_names.append(field_name)
+    if time_base_field_name not in field_names:
+        raise FieldNotExistInTableException(
+            {"message": "The field doesn't exist in this database", "field_name": time_base_field_name})
+    if not time_base_field_names:
+        raise NoTimeBaseFieldInTableException({"message": "There isn't any time base field in the table"})
+    if not time_base_field_name in time_base_field_names:
+        raise FieldNotApplicableForTimeBaseException(
+            {"message": "The specified field unsuitable for time base in the table",
+             "field_name": time_base_field_name})
+
+
+def check_if_plan_exist_yet(schema_name, table_name, time_base_field_name, encoding_parameters,
+                            feature_engineering_parameters):
+    existing_plans = database_handler.get_all_encoding_and_feature_engineering_plan()
+    for plan in existing_plans:
+        if plan.get("schema_name") == schema_name and plan.get("table_name") == table_name and plan.get(
+                "encoding_parameters") == encoding_parameters and plan.get(
+            "feature_engineering_parameters") == feature_engineering_parameters and plan.get(
+            "time_base_field_name") == time_base_field_name:
+            message = "Encoding and feature engineering plan exist yet"
+            id = plan.get("id")
+            raise IllegalArgumentException({"message": message, "id": id})
+
+
+def check_fields_and_possible_encoding_and_engineering(detailed_information_about_table,
+                                                       encoding_parameters, feature_engineering_parameters):
+    field_properties = detailed_information_about_table.get("fields")
+    number_of_int_and_float_type_fields = get_number_of_int_and_float_typed_fields(field_properties)
+    field_names_as_keys_in_encoding_parameters = encoding_parameters.keys()
+    number_of_encoding_parameters = len(field_names_as_keys_in_encoding_parameters)
+    number_of_fields_in_current_table = len(field_properties)
+    number_of_fields_to_be_encoded = number_of_fields_in_current_table - number_of_int_and_float_type_fields - 2  # fraud and id
+    if number_of_encoding_parameters < number_of_fields_to_be_encoded:
+        raise NoEnoughParameterException(
+            {"message": "there aren't enough parameter to encode", "parameter_name": encoding_parameters})
+    field_names_in_table = get_field_names_from_field_and_type_properties(field_properties)
+    for field in field_names_as_keys_in_encoding_parameters:
+        if field not in field_names_in_table:
+            raise FieldNotExistInTableException(
+                {"message": "The field doesn't exist in this database", "field_name": field})
+        if encoding_parameters.get(field) == "fraud_type":
+            check_if_field_applicable_for_fraud(detailed_information_about_table, field)
+
+    all_field_names = check_encoding_parameters(encoding_parameters,
+                                                detailed_information_about_table)
+    check_number_of_fraud_type_field(all_field_names, encoding_parameters)
+    if feature_engineering_parameters is not None:
+        check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parameters,
+                                                feature_engineering_parameters)
+
+
+def check_encoding_parameters(encoding_parameters, detailed_information_about_table):
+    all_field_names = list()
+    field_properties = detailed_information_about_table.get("fields")
+    for field_property in field_properties:
+        field_name = field_property.get("name")
+        all_field_names.append(field_name)
+        if field_name in encoding_parameters.keys():
+            planned_encoding_type = encoding_parameters.get(field_name)
+            if (planned_encoding_type == "julian" and field_property.get(
+                    "type") != "datetime") or (planned_encoding_type == "label_encoder" and field_property.get(
+                "type") != "varchar") or (planned_encoding_type == "int" and "int" not in field_property.get(
+                database_module.OTHER_APPLICABLE_ENCODING_TYPES)) or (
+                    planned_encoding_type == "float" and "float" not in field_property.get(
+                database_module.OTHER_APPLICABLE_ENCODING_TYPES)) or planned_encoding_type not in ["julian",
+                                                                                                   "label_encoder",
+                                                                                                   "int",
+                                                                                                   "float",
+                                                                                                   "fraud_type",
+                                                                                                   "omit"]:
+                raise EncodingNotApplicableException(
+                    {"message": "the planned encoding can not applicable to this field", "field_name": field_name,
+                     "planned_encoding_type": planned_encoding_type})
+    return all_field_names
+
+
+def get_number_of_int_and_float_typed_fields(field_and_type_and_other_eligible_type_for_encoding_collection):
+    int_types = ("tinyint", "smallint", "mediumint", "int", "bigint")
+    float_types = ("float", "double")
+    number_of_int_and_float_typed_field = 0
+    for field_property in field_and_type_and_other_eligible_type_for_encoding_collection:
+        current_type = field_property.get("type")
+        if current_type in int_types or current_type in float_types:
+            number_of_int_and_float_typed_field += 1
+    return number_of_int_and_float_typed_field
+
+
+def check_if_field_applicable_for_fraud(detailed_information_about_table, current_field_name):
+    fraud_candidates = detailed_information_about_table.get("fraud_candidates")
+    applicable_field_names_for_fraud = list()
+    for fraud_candidate in fraud_candidates:
+        applicable_field_names_for_fraud.append(fraud_candidate.get("name"))
+    if current_field_name not in applicable_field_names_for_fraud:
+        raise FieldNotApplicableForFraudException(
+            {"message": "the field not applicable for fraud", "field_name": current_field_name})
+
+
+def check_number_of_fraud_type_field(field_names, encoding_parameters):
+    number_of_fraud_type_field = 0
+    for field_name in field_names:
+        if encoding_parameters.get(field_name) == "fraud_type":
+            number_of_fraud_type_field += 1
+    if number_of_fraud_type_field == 0:
+        raise NoFraudTypeFieldException()
+    if number_of_fraud_type_field > 1:
+        raise TooManyFraudTypeFieldException()
+
+
+def check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parameters,
+                                            feature_engineering_parameters):
+    field_name_to_be_engineered = feature_engineering_parameters.get("chosen_feature_field")
+    intervals = feature_engineering_parameters.get("intervals")
+    if field_name_to_be_engineered is None:
+        message = "field name for engineering is missing"
+        raise FeatureEngineeringNotApplicableException({"message": message})
+    if intervals is None or not intervals:
+        message = "interval list for engineering is missing or empty"
+        raise FeatureEngineeringNotApplicableException({"message": message})
+    field_properties = detailed_information_about_table.get("fields")
+    field_names = get_field_names_from_field_and_type_properties(field_properties)
+    if field_name_to_be_engineered not in field_names:
+        message = "the field name to be engineered doesn't exist in the table"
+        logger.error(message + f", field name to be engineered: {field_name_to_be_engineered}")
+        raise FeatureEngineeringNotApplicableException({"message": message})
+    for field_property in field_properties:
+        field_name = field_property.get("name")
+        field_type = field_property.get("type")
+        if field_name == field_name_to_be_engineered:
+            if field_type not in ["int", "float"]:
+                message = "only int or float type field can be used for feature engineering"
+                raise FeatureEngineeringNotApplicableException(
+                    {"message": message, "field name": field_name, "field's type": field_type})
+            if encoding_parameters.get(field_name) == "omit":
+                message = f"the {field_name} field has been omitted during encoding process, it can't be used for feature engineering"
+                raise FeatureEngineeringNotApplicableException({"message": message})
+    if len(intervals) == 0:
+        message = "time intervals list is empty"
+        raise FeatureEngineeringNotApplicableException({"message": message})
+
+
+def get_field_names_from_field_and_type_properties(field_and_type_properties):
+    result = list()
+    for item in field_and_type_properties:
+        result.append(item.get("name"))
+    return result
+
+
+def process_fit(parameter):
+    message = dict()
+    transaction_identifier_field_name = parameter.get("transaction_identifier_field_name")
+    train_task_id = parameter.get("train_task_id")
+    logger.info(f"train process started, train_task_id: {train_task_id}")
+    planned_encoding_and_feature_engineering_id = parameter.get("planned_encoding_and_feature_engineering_id")
+    schema_name, table_name, detailed_information_about_table, time_base_field_name, encoding_parameters, feature_engineering_parameters = database_handler.get_parameters_from_plan_by_id(
+        planned_encoding_and_feature_engineering_id)
+    feature_selector = parameter.get("feature_selector")
+    sampler = parameter.get("sampler")
+    scaler = parameter.get("scaler")
+    model = parameter.get("model")
+    split_test_size = parameter.get("split_test_size")
+    used_cpu_core = parameter.get("used_cpu_core")
+
+    raw_dataset_id = database_handler.get_raw_dataset_id(schema_name, table_name)
+    encoding_id = database_handler.get_encoding_id_by_json_content(encoding_parameters)
+
+    encoded_table_registry_id, encoded_table_name, encoded_field_names, label_encoder_registry = database_handler.get_encoded_table_properties(
+        raw_dataset_id, encoding_id, time_base_field_name)
+    if encoded_table_name is not None:
+        logger.info(f"database encoded yet, train task id: {train_task_id}")
+        send_async_message(train_task_id, ENCODING, PREVIOUSLY_DONE)
+        dataset_to_train = database_handler.get_records_for_processing(schema_name, encoded_table_name,
+                                                                       time_base_field_name)
+    else:
+        start_encoding = time.time()
+        logger.info(f"database encoding started, train task id: {train_task_id}")
+        send_async_message(train_task_id, ENCODING, STARTED)
+        try:
+            dataset_to_train, encoded_table_registry_id, encoded_table_name, encoded_field_names, label_encoder_registry = database_handler.encode(
+                raw_dataset_id, schema_name, table_name, encoding_id, encoding_parameters, time_base_field_name)
+        except Exception as e:
+            logger.error("Error occured: " + str(e))
+
+        finish_encoding = time.time()
+        encoding_process_time = finish_encoding - start_encoding
+        logger.info(
+            f"database encoding finished, train task id: {train_task_id}, processing time: {encoding_process_time} sec")
+        send_async_message(train_task_id, ENCODING, FINISHED)
+
+    feature_engineered_insert_script_using_in_prediction = None
+    if feature_engineering_parameters is None:
+        logger.info("train without feature engineering")
+    else:
+        feature_engineering_id = database_handler.get_feature_engineering_id_by_json_content(
+            feature_engineering_parameters)
+        table_name, feature_engineered_insert_script = database_handler.get_table_name_from_feature_engineering_registry(
+            encoded_table_registry_id,
+            feature_engineering_id)
+        if table_name is not None:
+            logger.info(f"database feature engineered yet, train task id: {train_task_id}")
+            send_async_message(train_task_id, FEATURE_ENGINEERING, PREVIOUSLY_DONE)
+            dataset_to_train = database_handler.get_records_for_processing(schema_name, table_name,
+                                                                           time_base_field_name)
+        else:
+            start_feature_engineering = time.time()
+            logger.info(f"database feature engineering started, train task id: {train_task_id}")
+            send_async_message(train_task_id, FEATURE_ENGINEERING, STARTED)
+            fraud_type_field_name = get_fraud_type_field_name(encoding_parameters)
+            dataset_to_train, table_name, feature_engineered_insert_script_using_in_prediction = database_handler.feature_engineer(
+                schema_name, encoded_table_name, fraud_type_field_name,
+                feature_engineering_id,
+                dataset_to_train, time_base_field_name,
+                encoded_field_names,
+                feature_engineering_parameters,
+                encoded_table_registry_id, used_cpu_core)
+            finish_feature_engineering = time.time()
+            feature_engineering_process_time = finish_feature_engineering - start_feature_engineering
+            logger.info(
+                f"database feature engineering finished, train task id: {train_task_id}, processing time: {feature_engineering_process_time} sec")
+            send_async_message(train_task_id, FEATURE_ENGINEERING, FINISHED)
+
+    features = dataset_to_train[:, :-1]
+    labels = dataset_to_train[:, -1:]
+    labels = labels.astype(int)
+    labels = column_or_1d(labels)
+    train_features, test_features, train_labels, test_labels = train_test_split(features, labels,
+                                                                                test_size=split_test_size,
+                                                                                random_state=0)
+
+    logger.info("sampling started")
+    send_async_message(train_task_id, SAMPLING, STARTED)
+    start_sampling = time.time()
+    sampled_train_features, sampled_train_labels = sampler.fit_resample(train_features, train_labels)
+    finish_sampling = time.time()
+    sampling_proessing_time = finish_sampling - start_sampling
+    logger.info(
+        f"sampling finished, train task id: {train_task_id}, processing time: {sampling_proessing_time} sec")
+    send_async_message(train_task_id, SAMPLING, FINISHED)
+
+    pipeline = Pipeline(
+        [('scaler', scaler), ('featureSelector', feature_selector), ('model', model)]
+    )
+    logger.info(f"pipeline's training started, train task id: {train_task_id}")
+    send_async_message(train_task_id, FITTING, STARTED)
+    start_fit = time.time()
+    pipeline.fit(sampled_train_features, sampled_train_labels)
+    finish_fit = time.time()
+    fit_processing_time = finish_fit - start_fit
+    logger.info(
+        f"pipeline's training finished, train task id: {train_task_id} ,processing time: {fit_processing_time} sec")
+    send_async_message(train_task_id, FITTING, FINISHED)
+    predicted_labels = pipeline.predict(test_features)
+    conf_matrix = confusion_matrix(test_labels, predicted_labels)
+    logger.info(f"Confusion Matrix: {conf_matrix}, train_task_id: {train_task_id}")
+
+    estimator = dict()
+    estimator["pipeline"] = pipeline
+    estimator["label_encoder_registry"] = label_encoder_registry
+    estimator["encoding_parameters"] = encoding_parameters
+    estimator["feature_engineering_parameters"] = feature_engineering_parameters
+    estimator["encoded_field_names"] = encoded_field_names
+    estimator["time_base_field_name"] = time_base_field_name
+    estimator["schema_name"] = schema_name
+    estimator["feature_engineered_table_name"] = table_name
+    estimator["detailed_information_about_table"] = detailed_information_about_table
+    estimator["feature_engineered_insert_script"] = feature_engineered_insert_script_using_in_prediction
+
+    send_async_message(train_task_id, ESTIMATOR_PERSISTING, STARTED)
+    estimator_id = database_handler.persist_estimator(train_task_id, transaction_identifier_field_name, estimator)
+    logger.info(
+        f"estimator (pipeline, label encoders, encoding parameters, feature engineering parameters etc.) persisted, train task id: {train_task_id}, estimator_id: {estimator_id}")
+    send_async_message(train_task_id, ESTIMATOR_PERSISTING, FINISHED)
+
+    send_async_message(train_task_id, METRIC_PERSISTING, STARTED)
+    calculate_and_save_metrics(estimator_id, conf_matrix, test_labels, predicted_labels)
+    logger.info(f"Metrics persisted, train task id: {train_task_id}")
+    send_async_message(train_task_id, METRIC_PERSISTING, FINISHED)
+
+    database_handler.set_train_task_status(train_task_id, DONE)
+    logger.info(f"train process finished, train_task_id: {train_task_id}")
+
+
+def get_fraud_type_field_name(encoding_parameters):
+    for field_name, encoding_type in encoding_parameters.items():
+        if encoding_type == FRAUD_TYPE:
+            return field_name
+
+
+def calculate_and_save_metrics(estimator_id, conf_matrix, test_labels, predicted_labels):
+    TP = int(conf_matrix[0][0])
+    FP = int(conf_matrix[0][1])
+    FN = int(conf_matrix[1][0])
+    TN = int(conf_matrix[1][1])
+    temp = TP + FN
+    sensitivity = 0
+    if temp != 0:
+        sensitivity = TP / (TP + FN)
+    temp = TN + FP
+    specificity = 0
+    if temp != 0:
+        specificity = TN / (TN + FP)
+    accuracy = accuracy_score(test_labels, predicted_labels)
+    balanced_accuracy = balanced_accuracy_score(test_labels, predicted_labels)
+    precision = 0
+    temp = TP + FP
+    if temp != 0:
+        precision = TP / (TP + FP)
+    recall = recall_score(test_labels, predicted_labels)
+    temp = TP + FN
+    PPV = 0
+    if temp != 0:
+        PPV = TP / (TP + FN)
+    temp = TN + FN
+    NPV = 0
+    if temp != 0:
+        NPV = TN / (TN + FN)
+    temp = FN + TP
+    FNR = 0
+    if temp != 0:
+        FNR = FN / (FN + TP)
+    temp = FP + TN
+    FPR = 0
+    if temp != 0:
+        FPR = FP / (FP + TN)
+    FDR = 0
+    temp = FP + TP
+    if temp != 0:
+        FDR = FP / (FP + TP)
+    temp = FN + TN
+    FOR = 0
+    if temp != 0:
+        FOR = FN / (FN + TN)
+    f1 = f1_score(test_labels, predicted_labels)
+    f_05 = calculateF(0.5, precision, recall)
+    f2 = calculateF(2, precision, recall)
+    temp = math.sqrt(TP + FP) * math.sqrt(TP + FN) * math.sqrt(TN + FP) * math.sqrt(TN + FN)
+    MCC = 0
+    if temp != 0:
+        MCC = (TP * TN - FP * FN) / temp
+    ROCAUC = roc_auc_score(test_labels, predicted_labels)
+    Youdens_statistic = sensitivity + specificity - 1
+    database_handler.persist_metrics(estimator_id, TP, FP, TN, FN, sensitivity, specificity, accuracy,
+                                     balanced_accuracy,
+                                     precision, recall, PPV, NPV, FNR, FPR, FDR, FOR, f1, f_05, f2, MCC, ROCAUC,
+                                     Youdens_statistic)
+
+
+def calculateF(beta, precision, recall):
+    """F érték számítása"""
+    temp = beta * beta * precision + recall
+    if temp != 0:
+        f_beta = (1 + beta) * (1 + beta) * precision * recall / temp
+    else:
+        f_beta = 0
+    return f_beta
+
+
+if __name__ == "__main__":
+    log_file = os.getenv("TRAIN_LOG_FILE", "/home/tomi/log/train.log")
+    log_level = os.getenv("TRAIN_LOG_LEVEL", "DEBUG")
+    rabbitmq_url = os.getenv("RABBITMQ_URL", "localhost")
+    database_url = os.getenv("MYSQL_DATABASE_URL", "localhost")
+    database_user = os.getenv("MYSQL_USER", "root")
+    database_password = os.getenv("MYSQL_ROOT_PASSWORD", "pwd")
+    file_handler = logging.FileHandler(log_file)
+    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+    file_handler.setFormatter(formatter)
+    logger = logging.getLogger("train.server")
+    logger.setLevel(log_level)
+    logger.addHandler(file_handler)
+    logger.debug("RabbitMQ: " + rabbitmq_url)
+    logger.debug("MYSQL_DATABASE_URL: " + database_url)
+    logger.debug("MYSQL_USER :" + database_user)
+    logger.debug("MYSQL_PASSWORD: " + database_password)
+    logger.debug("Train application started")
+    try:
+        database_handler = database_module.Handler(database_url, database_user, database_password)
+        database_handler.create_common_fraud_schemas()
+    except Exception as e:
+        logger.error("Can not connect to database")
+    logger.debug("train modul started")
+    app.run(host='0.0.0.0', port=8085, debug=True)
diff --git a/Train/src/test_ddl_build_module.py b/Train/src/test_ddl_build_module.py
new file mode 100644 (file)
index 0000000..5adc9b8
--- /dev/null
@@ -0,0 +1,103 @@
+import unittest
+import ddl_build_module
+# from src.database_module import IllegalArgumentException
+import exception_module
+
+
+class MyTestCase(unittest.TestCase):
+    def test_create_insert_script(self):
+        script = "INSERT INTO card.encoded (time,amount,vendor) VALUES(%s,%s,%s)"
+        builder = ddl_build_module.DdlCommandBuilder()
+        fields = ["time", "amount", "vendor"]
+        built_script = builder.create_insert_into_encoded_or_feature_engineered_script("card", "encoded", fields)
+        self.assertEqual(built_script, script)  # add assertion here
+
+    def test_create_encoded_table_script(self):
+        script = "CREATE TABLE IF NOT EXISTS card.encoded(id BIGINT NOT NULL AUTO_INCREMENT,time DOUBLE PRECISION,amount DOUBLE PRECISION,vendor DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        fields = ["time", "amount", "vendor"]
+        built_script = builder.build_create_encoded_or_engineered_table_script("card", "encoded", fields)
+        self.assertEqual(script, built_script)
+
+    def test_build_create_generic_train_database_script(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME(6),amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(255)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        built_script = builder.build_create_generic_train_database_script(schema_name, table_name, field_type_by_name,True)
+        self.assertEqual(script, built_script)
+
+    def test_build_create_generic_train_database_script_if_missing_begin_bracket_thorws_exception(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar255)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_missing_end_bracket_thorws_exception(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(255",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_missing_varchar_number(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar()",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_varchar_number_not_int_type(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(1.5)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_varchar_number_not_number_type(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(a)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/Train/src/test_encoding_module.py b/Train/src/test_encoding_module.py
new file mode 100644 (file)
index 0000000..89c487b
--- /dev/null
@@ -0,0 +1,98 @@
+import unittest
+
+import encoding_module
+
+encoding_parameters = {
+    "card_number": "label_encoder",
+    "transaction_type": "omit",
+    "fraud": "fraud_type"
+}
+
+detailed_information_about_table = {
+    "fields": [
+        {
+            "name": "id",
+            "other_eligible_type_for_encoding": [],
+            "type": "bigint"
+        },
+        {
+            "name": "card_number",
+            "other_eligible_type_for_encoding": [
+                "float"
+            ],
+            "type": "varchar"
+        },
+        {
+            "name": "transaction_type",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        },
+        {
+            "name": "timestamp",
+            "other_eligible_type_for_encoding": [],
+            "type": "datetime"
+        },
+        {
+            "name": "amount",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        },
+        {
+            "name": "currency_name",
+            "other_eligible_type_for_encoding": [],
+            "type": "varchar"
+        },
+        {
+            "name": "response_code",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        },
+        {
+            "name": "country_name",
+            "other_eligible_type_for_encoding": [],
+            "type": "varchar"
+        },
+        {
+            "name": "vendor_code",
+            "other_eligible_type_for_encoding": [
+                "float",
+                "int"
+            ],
+            "type": "varchar"
+        },
+        {
+            "name": "fraud",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        }
+    ],
+    "fraud_candidates": [
+        {
+            "fraud_number": 601,
+            "name": "fraud",
+            "no_fraud_number": 11314
+        }
+    ],
+    "primary_key": "id",
+    "record_number": 11915
+}
+
+
+class TestEncoding(unittest.TestCase):
+    def test_create_modified_fields(self):
+        original_fields = ["id", "card_number", "transaction_type", "timestamp", "amount", "currency_name",
+                           "response_code", "country_name", "vendor_code", "fraud"]
+        database_encoder = encoding_module.DataBaseEncoder(None, original_fields)
+        modified_fields = database_encoder.build_encoded_field_names(encoding_parameters, "id")
+        self.assertEqual(8, len(modified_fields))
+
+    def test_get_field_type_before_encoding(self):
+        original_fields = ["id", "card_number", "transaction_type", "timestamp", "amount", "currency_name",
+                           "response_code", "country_name", "vendor_code", "fraud"]
+        database_encoder = encoding_module.DataBaseEncoder(None, original_fields)
+        field_type = database_encoder.get_field_type_before_encoding("amount", detailed_information_about_table)
+        self.assertEqual("int", field_type)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Train/src/test_server_module.py b/Train/src/test_server_module.py
new file mode 100644 (file)
index 0000000..8260165
--- /dev/null
@@ -0,0 +1,227 @@
+import server
+import unittest
+
+encoding_parametrs = {
+    "card_number": "label_encoder",
+    "transaction_type": "omit",
+}
+
+detailed_information_about_table = {
+    "fields": [
+        {
+            "name": "id",
+            "other_eligible_type_for_encoding": [],
+            "type": "bigint"
+        },
+        {
+            "name": "card_number",
+            "other_eligible_type_for_encoding": [
+                "float"
+            ],
+            "type": "varchar"
+        },
+        {
+            "name": "transaction_type",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        },
+        {
+            "name": "timestamp",
+            "other_eligible_type_for_encoding": [],
+            "type": "datetime"
+        },
+        {
+            "name": "amount",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        },
+        {
+            "name": "currency_name",
+            "other_eligible_type_for_encoding": [],
+            "type": "varchar"
+        },
+        {
+            "name": "response_code",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        },
+        {
+            "name": "country_name",
+            "other_eligible_type_for_encoding": [],
+            "type": "varchar"
+        },
+        {
+            "name": "vendor_code",
+            "other_eligible_type_for_encoding": [
+                "float",
+                "int"
+            ],
+            "type": "varchar"
+        },
+        {
+            "name": "fraud",
+            "other_eligible_type_for_encoding": [],
+            "type": "int"
+        }
+    ],
+    "fraud_candidates": [
+        {
+            "fraud_number": 601,
+            "name": "fraud",
+            "no_fraud_number": 11314
+        }
+    ],
+    "primary_key": [
+        "id"
+    ],
+    "record_number": 11915
+}
+
+
+def get_fields_from_detailed_information(detailed_information_about_table):
+    result = list()
+    complex_fields = detailed_information_about_table.get("fields")
+    for complex_field_item in complex_fields:
+        field_name = complex_field_item.get("name")
+        result.append(field_name)
+    return result
+
+
+class TestServer(unittest.TestCase):
+    def test_get_number_of_int_and_float_typed_fields(self):
+        number_of_numerical_fields = server.get_number_of_int_and_float_typed_fields(
+            detailed_information_about_table.get("fields"))
+        self.assertEqual(number_of_numerical_fields, 5)
+
+    def test_to_many_fraud_type_in_parameters_then_throws_exception(self):
+        encoding_parameters = {
+            "fraud": "fraud_type",
+            "transaction_type": "fraud_type"
+        }
+        with self.assertRaises(server.TooManyFraudTypeFieldException):
+            fields = get_fields_from_detailed_information(detailed_information_about_table)
+            server.check_number_of_fraud_type_field(fields, encoding_parameters)
+
+    def test_if_no_fraud_type_in_parameters_then_then_throws_exception(self):
+        encoding_parameters = {}
+        fields = get_fields_from_detailed_information(detailed_information_about_table)
+        with self.assertRaises(server.NoFraudTypeFieldException):
+            server.check_number_of_fraud_type_field(fields, encoding_parameters)
+
+    def test_if_use_unknown_encoding_type_then_check_encoding_parameters_throws_exception(self):
+        encoding_parameters = {
+            "vendor_code": "double"
+        }
+        with self.assertRaises(server.EncodingNotApplicableException):
+            server.check_encoding_parameters(encoding_parameters, detailed_information_about_table)
+
+    def test_if_use_wrong_encoding_type_then_check_encoding_parameters_throws_exception(self):
+        encoding_parameters = {
+            "vendor_code": "julian"
+        }
+        with self.assertRaises(server.EncodingNotApplicableException):
+            server.check_encoding_parameters(encoding_parameters, detailed_information_about_table)
+
+    def test_if_feature_engineering_not_complete_then_check_throws_exception(self):
+        feature_engineering_parameters = {
+            "feature_name_for_engineering": "id",
+            "time_base_field_name": "timestamp"
+        }
+        encoding_parameters = {
+            "card_number": "label_encoder",
+            "transaction_type": "omit",
+        }
+        with self.assertRaises(server.FeatureEngineeringNotApplicableException):
+            server.check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parameters,
+                                                           feature_engineering_parameters)
+
+    def test_if_use_not_numerical_type_for_engineering_then_check_throws_exception(self):
+        feature_engineering_parameters = {
+            "feature_name_for_engineering": "id",
+            "intervals": [3, 7],
+            "time_base_field_name": "timestamp"
+        }
+        encoding_parameters = {
+            "card_number": "label_encoder",
+            "transaction_type": "omit",
+        }
+
+        with self.assertRaises(server.FeatureEngineeringNotApplicableException):
+            server.check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parameters,
+                                                           feature_engineering_parameters)
+
+    def test_if_use_not_datetime_type_field_for_engineering_time_base_then_check_throws_exception(self):
+        feature_engineering_parameters = {
+            "feature_name_for_engineering": "amount",
+            "intervals": [3, 7],
+            "time_base_field_name": "amount"
+        }
+        encoding_parameters = {
+            "card_number": "label_encoder",
+            "transaction_type": "omit",
+        }
+
+        with self.assertRaises(server.FeatureEngineeringNotApplicableException):
+            server.check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parametrs,
+                                                           feature_engineering_parameters)
+
+    def test_if_use_not_empty_interval_list_for_engineering_time_base_then_check_throws_exception(self):
+        feature_engineering_parameters = {
+            "feature_name_for_engineering": "amount",
+            "intervals": [],
+            "time_base_field_name": "timestamp"
+        }
+        encoding_parameters = {
+            "card_number": "label_encoder",
+            "transaction_type": "omit",
+        }
+
+        with self.assertRaises(server.FeatureEngineeringNotApplicableException):
+            server.check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parameters,
+                                                           feature_engineering_parameters)
+
+    def test_if_omit_feature_engineering_field_during_encoding_then_check_throws_exception(self):
+        feature_engineering_parameters = {
+            "feature_name_for_engineering": "amount",
+            "intervals": [2, 5],
+            "time_base_field_name": "timestamp"
+        }
+        encoding_parameters = {
+            "amount": "omit",
+            "transaction_type": "omit"
+        }
+
+        with self.assertRaises(server.FeatureEngineeringNotApplicableException):
+            server.check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parameters,
+                                                           feature_engineering_parameters)
+
+    def test_if_omit_feature_engineering_time_base_field_during_encoding_then_check_throws_exception(self):
+        feature_engineering_parameters = {
+            "feature_name_for_engineering": "amount",
+            "intervals": [2, 5],
+            "time_base_field_name": "timestamp"
+        }
+        encoding_parameters = {
+            "timestamp": "omit",
+            "transaction_type": "omit"
+        }
+
+        with self.assertRaises(server.FeatureEngineeringNotApplicableException):
+            server.check_if_feature_engineering_applicable(detailed_information_about_table, encoding_parameters,
+                                                           feature_engineering_parameters)
+
+    def test_get_fraud_type_field_name(self):
+        encoding_parameters = {
+            "card_number": "float",
+            "timestamp": "julian",
+            "currency_name": "label_encoder",
+            "fraud": "fraud_type",
+            "country_name": "omit",
+            "vendor_code": "label_encoder"
+        }
+        fraud_type_field_name=server.get_fraud_type_field_name(encoding_parameters)
+        self.assertEqual(fraud_type_field_name, "fraud")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Train/test_ddl_build_module.py b/Train/test_ddl_build_module.py
new file mode 100644 (file)
index 0000000..acb31d4
--- /dev/null
@@ -0,0 +1,103 @@
+import unittest
+import ddl_build_module
+from src.database_module import IllegalArgumentException
+import exception_module
+
+
+class MyTestCase(unittest.TestCase):
+    def test_create_insert_script(self):
+        script = "INSERT INTO card.encoded (time,amount,vendor) VALUES(%s,%s,%s)"
+        builder = ddl_build_module.DdlCommandBuilder()
+        fields = ["time", "amount", "vendor"]
+        built_script = builder.create_insert_into_encoded_or_feature_engineered_script("card", "encoded", fields)
+        self.assertEqual(built_script, script)  # add assertion here
+
+    def test_create_encoded_table_script(self):
+        script = "CREATE TABLE IF NOT EXISTS card.encoded(id BIGINT NOT NULL AUTO_INCREMENT,time DOUBLE PRECISION,amount DOUBLE PRECISION,vendor DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        fields = ["time", "amount", "vendor"]
+        built_script = builder.build_create_encoded_or_engineered_table_script("card", "encoded", fields)
+        self.assertEqual(script, built_script)
+
+    def test_build_create_generic_train_database_script(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME(6),amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(255)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        built_script = builder.build_create_generic_train_database_script(schema_name, table_name, field_type_by_name,True)
+        self.assertEqual(script, built_script)
+
+    def test_build_create_generic_train_database_script_if_missing_begin_bracket_thorws_exception(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar255)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_missing_end_bracket_thorws_exception(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(255",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_missing_varchar_number(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar()",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_varchar_number_not_int_type(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(1.5)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+    def test_build_create_generic_train_database_script_if_varchar_number_not_number_type(self):
+        script = "CREATE TABLE IF NOT EXISTS card.transaction(id BIGINT NOT NULL AUTO_INCREMENT,card_number VARCHAR(255),timestamp DATETIME,amount INT,val DOUBLE PRECISION,PRIMARY KEY (id)) ENGINE = InnoDB"
+        builder = ddl_build_module.DdlCommandBuilder()
+        schema_name="card"
+        table_name="transaction"
+        field_type_by_name={
+            "card_number":"varchar(a)",
+            "timestamp":"datetime",
+            "amount":"int",
+            "val":"double precision"
+        }
+        with self.assertRaises(exception_module.DdlBuildException):
+            builder.build_create_generic_train_database_script("card", "encoded", field_type_by_name,True)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/TrainCli/.mvn/wrapper/maven-wrapper.jar b/TrainCli/.mvn/wrapper/maven-wrapper.jar
new file mode 100644 (file)
index 0000000..c1dd12f
Binary files /dev/null and b/TrainCli/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/TrainCli/.mvn/wrapper/maven-wrapper.properties b/TrainCli/.mvn/wrapper/maven-wrapper.properties
new file mode 100644 (file)
index 0000000..b74bf7f
--- /dev/null
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/TrainCli/mvnw b/TrainCli/mvnw
new file mode 100755 (executable)
index 0000000..8a8fb22
--- /dev/null
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`\\unset -f command; \\command -v java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    fi
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+    if $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/TrainCli/mvnw.cmd b/TrainCli/mvnw.cmd
new file mode 100644 (file)
index 0000000..1d8ab01
--- /dev/null
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+               "$webclient = new-object System.Net.WebClient;"^
+               "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+               "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+               "}"^
+               "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+               "}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+  %JVM_CONFIG_MAVEN_PROPS% ^
+  %MAVEN_OPTS% ^
+  %MAVEN_DEBUG_OPTS% ^
+  -classpath %WRAPPER_JAR% ^
+  "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/TrainCli/pom.xml b/TrainCli/pom.xml
new file mode 100644 (file)
index 0000000..2f8943a
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.6.1</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>hu.user</groupId>
+    <artifactId>TrainCli</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>TrainCli</name>
+    <description>TrainCli</description>
+    <properties>
+        <java.version>11</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>1.3.2</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/TrainCli/src/main/java/hu/user/traincli/ParametersExistYetException.java b/TrainCli/src/main/java/hu/user/traincli/ParametersExistYetException.java
new file mode 100644 (file)
index 0000000..9bb1e06
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.traincli;
+
+public class ParametersExistYetException extends RuntimeException{
+
+       private String errorMessage;
+
+       public ParametersExistYetException(String errorMessage) {
+               this.errorMessage = errorMessage;
+       }
+
+       public String getErrorMessage() {
+               return errorMessage;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/TrainCliApplication.java b/TrainCli/src/main/java/hu/user/traincli/TrainCliApplication.java
new file mode 100644 (file)
index 0000000..660cb0d
--- /dev/null
@@ -0,0 +1,335 @@
+package hu.user.traincli;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import hu.user.traincli.dto.EncodingAndFeatureEngineeringParameters;
+import hu.user.traincli.dto.EstimatorIdentifiers;
+import hu.user.traincli.dto.FitParameters;
+import hu.user.traincli.dto.TrainTaskParameters;
+import hu.user.traincli.service.JsonConverter;
+import hu.user.traincli.service.TrainClient;
+import hu.user.traincli.status.TrainStatus;
+
+@SpringBootApplication
+public class TrainCliApplication implements CommandLineRunner {
+
+       private static final String GET_CPU = "get_cpu";
+       private static final String GET_SCHEMAS = "get_schemas";
+       private static final String GET_TABLES = "get_tables";
+       private static final String SCHEMA_NAME = "schema_name";
+       private static final String TABLE_NAME = "table_name";
+       private static final String GET_TABLE_PROPERTY = "get_table_property";
+       private static final String GET_RECORDS = "get_records";
+       private static final String LIMIT = "limit";
+       private static final String OFFSET = "offset";
+       private static final String NEW_E_AND_FE_PARAMETERS = "new_e_and_fe_parameters";
+       private static final String GET_E_AND_FE_PARAMETERS = "get_e_and_fe_parameters";
+       private static final String GET_FEATURE_SELECTOR = "get_feature_selector";
+       private static final String GET_SAMPLER = "get_sampler";
+       private static final String GET_SCALER = "get_scaler";
+       private static final String GET_MODEL = "get_model";
+       private static final String NEW_TRAIN_TASK = "new_train_task";
+       private static final String GET_TRAIN_TASK = "get_train_task";
+       private static final String FIT = "fit";
+       private static final String GET_METRICS = "get_metrics";
+       private static final String GET_ESTIMATOR = "get_estimator";
+       private static final String ESTIMATOR_ID = "estimator_id";
+       private static final String ID = "id";
+
+       @Autowired TrainClient trainClient;
+
+       public static void main(String[] args) {
+               SpringApplication.run(TrainCliApplication.class, args);
+       }
+
+       @Override
+       public void run(String... args) throws Exception {
+               TrainStatus trainStatus = trainClient.testLiveness();
+               if (trainStatus == TrainStatus.NOT_ACCESSIBLE) {
+                       System.out.println("Train module is not accessible");
+                       System.exit(0);
+               } else if (trainStatus == TrainStatus.RESPONSE_TIMEOUT) {
+                       System.out.println("Train's response time is to long");
+                       System.exit(0);
+               } else if (trainStatus == TrainStatus.OK) {
+                       System.out.println("Train module is accessible");
+               }
+               Map<String, String> queryParameters = new HashMap<>();
+               Integer id = null;
+               String filePath = null;
+               String jsonFileName = null;
+               String command = args[0];
+               switch (command) {
+               case GET_CPU:
+                       System.out.println("Available CPU core number: " + trainClient.getCpuCoreNumber());
+                       System.exit(0);
+               case GET_SCHEMAS:
+                       String[] schemas = trainClient.getSchemas();
+                       System.out.println("Schemas: ");
+                       Arrays.stream(schemas).forEach(System.out::println);
+                       System.exit(0);
+               case GET_TABLES:
+                       if (args.length < 2) {
+                               System.out.println("Schema name is missing");
+                               System.exit(0);
+                       } else {
+                               String schemaName = args[1];
+                               String[] tables = trainClient.getTables(Map.of(SCHEMA_NAME, schemaName));
+                               System.out.println("Tables is schema: ");
+                               Arrays.stream(tables).forEach(System.out::println);
+                               System.exit(0);
+                       }
+                       System.exit(0);
+               case GET_TABLE_PROPERTY:
+                       if (args.length < 2) {
+                               System.out.println("Schema name is missing");
+                       } else if (args.length < 3) {
+                               System.out.println("Table name is missing");
+                       } else {
+                               String schema_name = args[1];
+                               String table_name = args[2];
+                               System.out.println("Table properties: ");
+                               System.out.println(
+                                               trainClient.getTableProperties(Map.of(SCHEMA_NAME, schema_name, TABLE_NAME, table_name)));
+                       }
+                       System.exit(0);
+               case GET_RECORDS:
+                       String schemaName = null;
+                       String tableName = null;
+                       Integer limit = null;
+                       Integer offset = null;
+                       if (args.length < 2) {
+                               System.out.println("Schema name is missing");
+                               System.exit(0);
+                       } else {
+                               schemaName = args[1];
+                       }
+
+                       if (args.length < 3) {
+                               System.out.println("Table name is missing");
+                               System.exit(0);
+                       } else {
+                               tableName = args[2];
+                       }
+
+                       if (args.length < 4) {
+                               System.out.println("Limit is missing");
+                       } else {
+                               try {
+                                       limit = Integer.parseInt(args[3]);
+                               } catch (NumberFormatException e) {
+                                       System.out.println("The limit parameter isn't integer");
+                                       System.exit(0);
+                               }
+                       }
+                       if (args.length < 5) {
+                               System.out.println("Offset is missing");
+                       } else {
+                               try {
+                                       offset = Integer.parseInt(args[4]);
+                               } catch (NumberFormatException e) {
+                                       System.out.println("The offset parameter isn't integer");
+                                       System.exit(0);
+                               }
+                       }
+                       queryParameters.put(SCHEMA_NAME, schemaName);
+                       queryParameters.put(TABLE_NAME, tableName);
+                       if (limit != null) {
+                               queryParameters.put(LIMIT, String.valueOf(limit));
+                       }
+                       if (offset != null) {
+                               queryParameters.put(OFFSET, String.valueOf(offset));
+                       }
+                       System.out.println(trainClient.getRecords(queryParameters));
+                       System.exit(0);
+               case NEW_E_AND_FE_PARAMETERS:
+                       if (args.length < 2) {
+                               System.out.println("Path of JSON file is missing");
+                               System.exit(0);
+                       } else {
+                               filePath = args[1];
+                       }
+                       if (args.length < 3) {
+                               System.out.println("JSON file name is missing");
+                               System.exit(0);
+                       } else {
+                               jsonFileName = args[2];
+                       }
+                       try {
+                               EncodingAndFeatureEngineeringParameters encodingAndFeatureEngineeringParameters =
+                                               JsonConverter.jsonToJava(filePath, jsonFileName, EncodingAndFeatureEngineeringParameters.class);
+                               String response =
+                                               trainClient.addEncodingAndFeatureEngineeringParameters(encodingAndFeatureEngineeringParameters);
+                               System.out.println("Planned encoding and feature engineering id: " + response);
+                       } catch (ParametersExistYetException exception) {
+                               System.out.println(exception.getErrorMessage());
+                               System.exit(0);
+                       } catch (RuntimeException exception) {
+                               System.out.println(exception.getMessage());
+                               System.exit(0);
+                       }
+                       System.exit(0);
+               case GET_E_AND_FE_PARAMETERS:
+                       if (args.length > 1) {
+                               try {
+                                       id = Integer.parseInt(args[1]);
+                               } catch (NumberFormatException e) {
+                                       System.out.println("The id parameter isn't integer");
+                                       System.exit(0);
+                               }
+                               if (id != null) {
+                                       queryParameters.put("id", String.valueOf(id));
+                               }
+                       }
+                       System.out.println(trainClient.getEncodingAndFeatureEngineeringParameters(queryParameters));
+                       System.exit(0);
+               case GET_FEATURE_SELECTOR:
+                       System.out.println(trainClient.getFeatureSelectors());
+                       System.exit(0);
+               case GET_SAMPLER:
+                       System.out.println(trainClient.getSamplers());
+                       System.exit(0);
+               case GET_SCALER:
+                       System.out.println(trainClient.getScalers());
+                       System.exit(0);
+               case GET_MODEL:
+                       System.out.println(trainClient.getModels());
+                       System.exit(0);
+               case NEW_TRAIN_TASK:
+                       if (args.length < 2) {
+                               System.out.println("Path is missing");
+                               System.exit(0);
+                       } else {
+                               filePath = args[1];
+                       }
+                       if (args.length < 3) {
+                               System.out.println("JSON file name is missing");
+                               System.exit(0);
+                       } else {
+                               jsonFileName = args[2];
+                       }
+                       try {
+                               TrainTaskParameters trainTaskParameters =
+                                               JsonConverter.jsonToJava(filePath, jsonFileName, TrainTaskParameters.class);
+                               System.out.println("Train task id: " + trainClient.addTrainTask(trainTaskParameters));
+                               System.exit(0);
+                       } catch (ParametersExistYetException exception) {
+                               System.out.println(exception.getErrorMessage());
+                               System.exit(0);
+                       } catch (RuntimeException exception) {
+                               System.out.println(exception.getMessage());
+                               System.exit(0);
+                       }
+                       System.exit(0);
+               case GET_TRAIN_TASK:
+                       if (args.length > 1) {
+                               try {
+                                       id = Integer.parseInt(args[1]);
+                               } catch (NumberFormatException e) {
+                                       System.out.println("The id parameter isn't integer");
+                                       break;
+                               }
+                       }
+                       if (id != null) {
+                               queryParameters.put("id", String.valueOf(id));
+                       }
+                       System.out.println(trainClient.getTrainTask(queryParameters));
+                       System.exit(0);
+               case FIT:
+                       if (args.length < 2) {
+                               System.out.println("Path is missing");
+                               System.exit(0);
+                       } else {
+                               filePath = args[1];
+                       }
+                       if (args.length < 3) {
+                               System.out.println("JSON file name is missing");
+                               System.exit(0);
+                       } else {
+                               jsonFileName = args[2];
+                       }
+                       try {
+                               FitParameters fitParameters = JsonConverter.jsonToJava(filePath, jsonFileName, FitParameters.class);
+                               trainClient.fit(fitParameters);
+                       } catch (ParametersExistYetException exception) {
+                               System.out.println(exception.getErrorMessage());
+                               System.exit(0);
+                       } catch (RuntimeException exception) {
+                               System.out.println(exception.getMessage());
+                       }
+                       System.exit(0);
+               case GET_METRICS:
+                       if (args.length > 1) {
+                               try {
+                                       id = Integer.parseInt(args[1]);
+                               } catch (NumberFormatException e) {
+                                       System.out.println("The id parameter isn't integer");
+                                       System.exit(0);
+                               }
+                       }
+                       if (id != null) {
+                               queryParameters.put(ID, String.valueOf(id));
+                       }
+                       List<Map<String, Double>> metrics = trainClient.getMetrics(queryParameters);
+                       for (int i = 0; i < metrics.size(); i++) {
+                               System.out.println("Metrics");
+                               for (var item : metrics.get(i).entrySet()) {
+                                       System.out.println(item.getKey() + ": " + item.getValue());
+                               }
+                       }
+                       System.exit(0);
+               case GET_ESTIMATOR:
+                       if (args.length > 1) {
+                               try {
+                                       id = Integer.parseInt(args[1]);
+                               } catch (NumberFormatException e) {
+                                       System.out.println("The id parameter isn't integer");
+                                       System.exit(0);
+                               }
+                       }
+                       if (id != null) {
+                               queryParameters.put("ID", String.valueOf(id));
+                       }
+                       EstimatorIdentifiers[] estimatorIdentifiers = trainClient.getEstimatorIdentifiers();
+                       Arrays.stream(estimatorIdentifiers)
+                                       .forEach(e -> System.out.println("id: " + e.getId() + "; train_task_id: " + e.getTrainTaskId()));
+                       System.exit(0);
+               default:
+                       System.out.println("Available commmands:");
+                       System.out.println("Get CPU core number: get_cpu");
+                       System.out.println("Get shemas: get_schemas");
+                       System.out.println("Get tables of a given schema: get_tables <schema name>");
+                       System.out.println("Get properties of a given table: get_table_property <schema name> <table name>");
+                       System.out.println("Get records of a given table: get_records <schema name> <table name> <limit> <offset>");
+                       System.out.println("Add new encoding and feature engineering parameters: new_e_and_fe_parameters <path> <json file name>");
+                       System.out.println("Add new encoding and feature engineering parameters: new_e_and_fe_parameters <path> <json file name>");
+                       System.out.println("Get existing encoding and feature engineering parameters: get_e_and_fe_parameters");
+                       System.out.println("Get installed feature selectors: get_feature_selector");
+                       System.out.println("Get installed samplers: get_sampler");
+                       System.out.println("Get installed scalers: get_scaler");
+                       System.out.println("Get installed models: get_model");
+                       System.out.println("Add new train task: new_train_task");
+                       System.out.println("get existing train tasks: get_train_task");
+                       System.out.println("launch fit process: fit");
+                       System.out.println("get metrics: get_metrics");
+                       System.out.println("get estimators: get_estimator");
+                       System.exit(0);
+
+
+
+
+
+
+
+                       System.exit(0);
+               }
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/config/RabbitMqConfig.java b/TrainCli/src/main/java/hu/user/traincli/config/RabbitMqConfig.java
new file mode 100644 (file)
index 0000000..7e758f5
--- /dev/null
@@ -0,0 +1,22 @@
+package hu.user.traincli.config;
+
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RabbitMqConfig {
+
+    @Bean
+    public Queue queue() {
+        return new Queue("train");
+    }
+
+    @Bean
+    public MessageConverter messageConverter() {
+        return new Jackson2JsonMessageConverter();
+    }
+
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/config/RestConfig.java b/TrainCli/src/main/java/hu/user/traincli/config/RestConfig.java
new file mode 100644 (file)
index 0000000..214e766
--- /dev/null
@@ -0,0 +1,23 @@
+package hu.user.traincli.config;
+
+import java.time.Duration;
+
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+import hu.user.traincli.error.RestTemplateErrorHandler;
+
+@Configuration
+public class RestConfig {
+
+    @Bean
+    public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
+        RestTemplate restTemplate = builder
+                .setReadTimeout(Duration.ofSeconds(7))
+                .build();
+        return restTemplate;
+
+    }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/config/SchedulerConfig.java b/TrainCli/src/main/java/hu/user/traincli/config/SchedulerConfig.java
new file mode 100644 (file)
index 0000000..f1bb713
--- /dev/null
@@ -0,0 +1,27 @@
+package hu.user.traincli.config;
+
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.quartz.QuartzProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+
+@Configuration
+public class SchedulerConfig {
+       @Autowired
+       private ApplicationContext applicationContext;
+       @Bean
+       public SchedulerFactoryBean schedulerFactoryBean() {
+               SchedulerJobFactory jobFactory = new SchedulerJobFactory();
+               jobFactory.setApplicationContext(applicationContext);
+               SchedulerFactoryBean factory = new SchedulerFactoryBean();
+               factory.setOverwriteExistingJobs(true);
+               factory.setJobFactory(jobFactory);
+               return factory;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/config/SchedulerJobFactory.java b/TrainCli/src/main/java/hu/user/traincli/config/SchedulerJobFactory.java
new file mode 100644 (file)
index 0000000..8c8ece8
--- /dev/null
@@ -0,0 +1,24 @@
+package hu.user.traincli.config;
+
+import org.quartz.spi.TriggerFiredBundle;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.scheduling.quartz.SpringBeanJobFactory;
+
+public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
+
+    private AutowireCapableBeanFactory beanFactory;
+
+    @Override
+    public void setApplicationContext(final ApplicationContext context) {
+        beanFactory = context.getAutowireCapableBeanFactory();
+    }
+
+    @Override
+    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
+        final Object job = super.createJobInstance(bundle);
+        beanFactory.autowireBean(job);
+        return job;
+    }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/CommonResponse.java b/TrainCli/src/main/java/hu/user/traincli/dto/CommonResponse.java
new file mode 100644 (file)
index 0000000..811aa6b
--- /dev/null
@@ -0,0 +1,19 @@
+package hu.user.traincli.dto;
+
+public class CommonResponse {
+
+       private int id;
+
+       public int getId() {
+               return id;
+       }
+
+       public void setId(int id) {
+               this.id = id;
+       }
+
+       @Override
+       public String toString() {
+               return "CommonResponse{" + "id=" + id + '}';
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/CommonTrainParameters.java b/TrainCli/src/main/java/hu/user/traincli/dto/CommonTrainParameters.java
new file mode 100644 (file)
index 0000000..c5103ed
--- /dev/null
@@ -0,0 +1,17 @@
+package hu.user.traincli.dto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CommonTrainParameters {
+
+       private Map<String, String> parameters = new HashMap<>();
+
+       public Map<String, String> getParameters() {
+               return parameters;
+       }
+
+       public void setParameters(Map<String, String> parameters) {
+               this.parameters = parameters;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/EncodingAndFeatureEngineeringParameters.java b/TrainCli/src/main/java/hu/user/traincli/dto/EncodingAndFeatureEngineeringParameters.java
new file mode 100644 (file)
index 0000000..1b78aa7
--- /dev/null
@@ -0,0 +1,69 @@
+package hu.user.traincli.dto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class EncodingAndFeatureEngineeringParameters {
+
+       private int id;
+       private String schemaName;
+       private String tableName;
+       private String timeBaseFieldName;
+       private Map<String, String> encodingParameters = new HashMap<>();
+       private FeatureEnginneringParameters featureEnginneringParameters;
+
+       public int getId() {
+               return id;
+       }
+
+       public void setId(int id) {
+               this.id = id;
+       }
+
+       public String getSchemaName() {
+               return schemaName;
+       }
+
+       @JsonSetter("schema_name")
+       public void setSchemaName(String schemaName) {
+               this.schemaName = schemaName;
+       }
+
+       public String getTableName() {
+               return tableName;
+       }
+
+       @JsonSetter("table_name")
+       public void setTableName(String tableName) {
+               this.tableName = tableName;
+       }
+
+       public String getTimeBaseFieldName() {
+               return timeBaseFieldName;
+       }
+
+       @JsonSetter("time_base_field_name")
+       public void setTimeBaseFieldName(String timeBaseFieldName) {
+               this.timeBaseFieldName = timeBaseFieldName;
+       }
+
+       public Map<String, String> getEncodingParameters() {
+               return encodingParameters;
+       }
+
+       @JsonSetter("encoding_parameters")
+       public void setEncodingParameters(Map<String, String> encodingParameters) {
+               this.encodingParameters = encodingParameters;
+       }
+
+       public FeatureEnginneringParameters getFeatureEnginneringParameters() {
+               return featureEnginneringParameters;
+       }
+
+       @JsonSetter("feature_engineering_parameters")
+       public void setFeatureEnginneringParameters(FeatureEnginneringParameters featureEnginneringParameters) {
+               this.featureEnginneringParameters = featureEnginneringParameters;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/EstimatorIdentifiers.java b/TrainCli/src/main/java/hu/user/traincli/dto/EstimatorIdentifiers.java
new file mode 100644 (file)
index 0000000..a5b1ffe
--- /dev/null
@@ -0,0 +1,26 @@
+package hu.user.traincli.dto;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class EstimatorIdentifiers {
+
+       private int id;
+       private int trainTaskId;
+
+       public int getId() {
+               return id;
+       }
+
+       public void setId(int id) {
+               this.id = id;
+       }
+
+       public int getTrainTaskId() {
+               return trainTaskId;
+       }
+
+       @JsonSetter("train_task_id")
+       public void setTrainTaskId(int trainTaskId) {
+               this.trainTaskId = trainTaskId;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/FeatureEnginneringParameters.java b/TrainCli/src/main/java/hu/user/traincli/dto/FeatureEnginneringParameters.java
new file mode 100644 (file)
index 0000000..a30c736
--- /dev/null
@@ -0,0 +1,26 @@
+package hu.user.traincli.dto;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FeatureEnginneringParameters {
+
+       private String chosesFeatureField;
+       private int[] intervals;
+
+       public String getChosesFeatureField() {
+               return chosesFeatureField;
+       }
+
+       @JsonSetter("chosen_feature_field")
+       public void setChosesFeatureField(String chosesFeatureField) {
+               this.chosesFeatureField = chosesFeatureField;
+       }
+
+       public int[] getIntervals() {
+               return intervals;
+       }
+
+       public void setIntervals(int[] intervals) {
+               this.intervals = intervals;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/FieldProperty.java b/TrainCli/src/main/java/hu/user/traincli/dto/FieldProperty.java
new file mode 100644 (file)
index 0000000..417e0de
--- /dev/null
@@ -0,0 +1,48 @@
+package hu.user.traincli.dto;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FieldProperty {
+
+       private String name;
+       private String[] otherApplicableEncodingTypes;
+       private String type;
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public String[] getOtherApplicableEncodingTypes() {
+               return otherApplicableEncodingTypes;
+       }
+
+       @JsonSetter("other_applicable_encoding_types")
+       public void setOtherApplicableEncodingTypes(String[] otherApplicableEncodingTypes) {
+               this.otherApplicableEncodingTypes = otherApplicableEncodingTypes;
+       }
+
+       public String getType() {
+               return type;
+       }
+
+       public void setType(String type) {
+               this.type = type;
+       }
+
+       @Override
+       public String toString() {
+               return "{" + "\n" + "name : " + name + "," + '\n' + "otherApplicableEncodingTypes : " + summaryzeOtherApplicableEncodingTypes(
+                               otherApplicableEncodingTypes) + "," + "\n" + "type : " + type + "\n" + "}";
+       }
+
+       private String summaryzeOtherApplicableEncodingTypes(String[] types) {
+               return Arrays.stream(types).collect(Collectors.joining(", "));
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/FitParameters.java b/TrainCli/src/main/java/hu/user/traincli/dto/FitParameters.java
new file mode 100644 (file)
index 0000000..d328489
--- /dev/null
@@ -0,0 +1,27 @@
+package hu.user.traincli.dto;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FitParameters {
+
+       private int trainTaskId;
+       private int usedCpuCore;
+
+       public int getTrainTaskId() {
+               return trainTaskId;
+       }
+
+       @JsonSetter("train_task_id")
+       public void setTrainTaskId(int trainTaskId) {
+               this.trainTaskId = trainTaskId;
+       }
+
+       public int getUsedCpuCore() {
+               return usedCpuCore;
+       }
+
+       @JsonSetter("used_cpu_core")
+       public void setUsedCpuCore(int usedCpuCore) {
+               this.usedCpuCore = usedCpuCore;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/FraudCandidate.java b/TrainCli/src/main/java/hu/user/traincli/dto/FraudCandidate.java
new file mode 100644 (file)
index 0000000..736b2da
--- /dev/null
@@ -0,0 +1,41 @@
+package hu.user.traincli.dto;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FraudCandidate {
+
+       private int fraudNumber;
+       private String name;
+       private int NoFraudNumber;
+
+       public int getFraudNumber() {
+               return fraudNumber;
+       }
+
+       @JsonSetter("fraud_number")
+       public void setFraudNumber(int fraudNumber) {
+               this.fraudNumber = fraudNumber;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public int getNoFraudNumber() {
+               return NoFraudNumber;
+       }
+
+       @JsonSetter("no_fraud_number")
+       public void setNoFraudNumber(int noFraudNumber) {
+               NoFraudNumber = noFraudNumber;
+       }
+
+       @Override
+       public String toString() {
+               return "FraudCandidate: {\n" + "fraudNumber: " + fraudNumber + ",\n" + "name: '" + name + ",\n" + "NoFraudNumber: " + NoFraudNumber + "\n" + "}";
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/LivenessDto.java b/TrainCli/src/main/java/hu/user/traincli/dto/LivenessDto.java
new file mode 100644 (file)
index 0000000..0a10551
--- /dev/null
@@ -0,0 +1,20 @@
+package hu.user.traincli.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class LivenessDto {
+
+       private String response;
+
+       public LivenessDto(@JsonProperty(value = "response",required = true) String response) {
+               this.response = response;
+       }
+
+       public String getResponse() {
+               return response;
+       }
+
+       public void setResponse(String response) {
+               this.response = response;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/TableProperties.java b/TrainCli/src/main/java/hu/user/traincli/dto/TableProperties.java
new file mode 100644 (file)
index 0000000..fcf3555
--- /dev/null
@@ -0,0 +1,79 @@
+package hu.user.traincli.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class TableProperties {
+
+       private List<FieldProperty> fields = new ArrayList<>();
+       private List<FraudCandidate> fraudCandidates = new ArrayList<>();
+       private String primaryKey;
+       private int recordNumber;
+       private String lineSeparator = System.lineSeparator();
+
+       public List<FieldProperty> getFields() {
+               return fields;
+       }
+
+       public void setFields(List<FieldProperty> fields) {
+               this.fields = fields;
+       }
+
+       public List<FraudCandidate> getFraudCandidates() {
+               return fraudCandidates;
+       }
+
+       @JsonSetter("fraud_candidates")
+       public void setFraudCandidates(List<FraudCandidate> fraudCandidates) {
+               this.fraudCandidates = fraudCandidates;
+       }
+
+       public String getPrimaryKey() {
+               return primaryKey;
+       }
+
+       @JsonSetter("primary_key")
+       public void setPrimaryKey(String primaryKey) {
+               this.primaryKey = primaryKey;
+       }
+
+       public int getRecordNumber() {
+               return recordNumber;
+       }
+
+       @JsonSetter("record_number")
+       public void setRecordNumber(int recordNumber) {
+               this.recordNumber = recordNumber;
+       }
+
+       @Override
+       public String toString() {
+               return "TableProperties: {" + convertToString() + "\n}";
+       }
+
+       private String convertToString() {
+               StringBuilder stringBuilder = new StringBuilder();
+               stringBuilder.append("fields: [");
+               stringBuilder.append(lineSeparator);
+               stringBuilder.append(fields.stream().map(Objects::toString).collect(Collectors.joining(",\n")));
+               stringBuilder.append(",");
+               stringBuilder.append(lineSeparator);
+               stringBuilder.append(fraudCandidates.stream().map(Object::toString).collect(Collectors.joining(",\n")));
+               stringBuilder.append(lineSeparator);
+               stringBuilder.append("]");
+               stringBuilder.append(",");
+               stringBuilder.append(lineSeparator);
+               stringBuilder.append("Primary key: ");
+               stringBuilder.append(primaryKey);
+               stringBuilder.append(",");
+               stringBuilder.append(lineSeparator);
+               stringBuilder.append("Record number: ");
+               stringBuilder.append(recordNumber);
+//             stringBuilder.append(lineSeparator);
+               return stringBuilder.toString();
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/dto/TrainTaskParameters.java b/TrainCli/src/main/java/hu/user/traincli/dto/TrainTaskParameters.java
new file mode 100644 (file)
index 0000000..bcd53a5
--- /dev/null
@@ -0,0 +1,92 @@
+package hu.user.traincli.dto;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class TrainTaskParameters {
+
+       private int plannedEncodingAndFeatureEngineeringId;
+       private int featureSelectorCode;
+       private int samplerCode;
+       private int scalerCode;
+       private int model_code;
+       private double test_size;
+       private String trainTaskName;
+       private String uniqueFieldName;
+
+       public int getPlannedEncodingAndFeatureEngineeringId() {
+               return plannedEncodingAndFeatureEngineeringId;
+       }
+
+       @JsonSetter("planned_encoding_and_feature_engineering_id")
+       public void setPlannedEncodingAndFeatureEngineeringId(int plannedEncodingAndFeatureEngineeringId) {
+               this.plannedEncodingAndFeatureEngineeringId = plannedEncodingAndFeatureEngineeringId;
+       }
+
+       public int getFeatureSelectorCode() {
+               return featureSelectorCode;
+       }
+
+       @JsonSetter("feature_selector_code")
+       public void setFeatureSelectorCode(int featureSelectorCode) {
+               this.featureSelectorCode = featureSelectorCode;
+       }
+
+       public int getSamplerCode() {
+               return samplerCode;
+       }
+
+       @JsonSetter("sampler_code")
+       public void setSamplerCode(int samplerCode) {
+               this.samplerCode = samplerCode;
+       }
+
+       public int getScalerCode() {
+               return scalerCode;
+       }
+
+       @JsonSetter("scaler_code")
+       public void setScalerCode(int scalerCode) {
+               this.scalerCode = scalerCode;
+       }
+
+       public int getModel_code() {
+               return model_code;
+       }
+
+       @JsonSetter("model_code")
+       public void setModel_code(int model_code) {
+               this.model_code = model_code;
+       }
+
+       public double getTest_size() {
+               return test_size;
+       }
+
+       @JsonSetter("test_size")
+       public void setTest_size(double test_size) {
+               this.test_size = test_size;
+       }
+
+       public String getTrainTaskName() {
+               return trainTaskName;
+       }
+
+       @JsonSetter("train_task_name")
+       public void setTrainTaskName(String trainTaskName) {
+               this.trainTaskName = trainTaskName;
+       }
+
+       public String getUniqueFieldName() {
+               return uniqueFieldName;
+       }
+
+       @JsonSetter("unique_field_name")
+       public void setUniqueFieldName(String uniqueFieldName) {
+               this.uniqueFieldName = uniqueFieldName;
+       }
+
+       @Override
+       public String toString() {
+               return "TrainTaskParameters{" + "plannedEncodingAndFeatureEngineeringId=" + plannedEncodingAndFeatureEngineeringId + ", featureSelectorCode=" + featureSelectorCode + ", samplerCode=" + samplerCode + ", scalerCode=" + scalerCode + ", model_code=" + model_code + ", test_size=" + test_size + ", trainTaskName='" + trainTaskName + '\'' + ", uniqueFieldName='" + uniqueFieldName + '\'' + '}';
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/error/RestTemplateErrorHandler.java b/TrainCli/src/main/java/hu/user/traincli/error/RestTemplateErrorHandler.java
new file mode 100644 (file)
index 0000000..6ca794c
--- /dev/null
@@ -0,0 +1,30 @@
+package hu.user.traincli.error;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.web.client.DefaultResponseErrorHandler;
+
+public class RestTemplateErrorHandler extends DefaultResponseErrorHandler {
+
+       @Override
+       protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
+               byte[] responseBody = response.getBody().readAllBytes();
+               try {
+                       Reader reader = new InputStreamReader(new ByteArrayInputStream(responseBody), Charset.defaultCharset());
+                       CharBuffer buffer = CharBuffer.allocate(200);
+                       reader.read(buffer);
+                       reader.close();
+                       buffer.flip();
+                       System.out.println(buffer.toString());
+               }catch(IOException ex){
+                       System.out.println("Exception in handle error");;
+                       }
+               }
+       }
diff --git a/TrainCli/src/main/java/hu/user/traincli/event/TrainLiveCheckEvent.java b/TrainCli/src/main/java/hu/user/traincli/event/TrainLiveCheckEvent.java
new file mode 100644 (file)
index 0000000..857a096
--- /dev/null
@@ -0,0 +1,18 @@
+package hu.user.traincli.event;
+
+import org.springframework.context.ApplicationEvent;
+
+import hu.user.traincli.status.TrainStatus;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+public class TrainLiveCheckEvent extends ApplicationEvent {
+
+       private TrainStatus trainStatus;
+
+       public TrainLiveCheckEvent(Object source, TrainStatus trainStatus) {
+               super(source);
+               this.trainStatus = trainStatus;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/event/TrainTaskEvent.java b/TrainCli/src/main/java/hu/user/traincli/event/TrainTaskEvent.java
new file mode 100644 (file)
index 0000000..cd63330
--- /dev/null
@@ -0,0 +1,35 @@
+package hu.user.traincli.event;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class TrainTaskEvent {
+
+       private int id;
+       private String method;
+       private String status;
+
+       public int getId() {
+               return id;
+       }
+
+       @JsonSetter("train_task_id")
+       public void setId(int id) {
+               this.id = id;
+       }
+
+       public String getMethod() {
+               return method;
+       }
+
+       public void setMethod(String method) {
+               this.method = method;
+       }
+
+       public String getStatus() {
+               return status;
+       }
+
+       public void setStatus(String status) {
+               this.status = status;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/listenner/TrainQueueListener.java b/TrainCli/src/main/java/hu/user/traincli/listenner/TrainQueueListener.java
new file mode 100644 (file)
index 0000000..db68acb
--- /dev/null
@@ -0,0 +1,87 @@
+package hu.user.traincli.listenner;
+
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import hu.user.traincli.event.TrainTaskEvent;
+import hu.user.traincli.service.ProgressPresenter;
+import hu.user.traincli.train.ProgressName;
+import hu.user.traincli.train.ProgressState;
+import hu.user.traincli.train.ProgressStatus;
+import lombok.AllArgsConstructor;
+
+@Service
+@AllArgsConstructor
+public class TrainQueueListener {
+
+       @Autowired
+       private ProgressPresenter progressPresenter;
+
+       private final static String ENCODING = "ENCODING";
+       private final static String FEATURE_ENGINEERING = "FEATURE_ENGINEERING";
+       private final static String SAMPLING = "SAMPLING";
+       private final static String FITTING = "FITTING";
+       private final static String ESTIMATOR_PERSISTING = "ESTIMATOR_PERSISTING";
+       private final static String METRIC_PERSISTING = "METRIC_PERSISTING";
+       private final static String PREVIOUSLY_DONE = "PREVIOUSLY_DONE";
+       private final static String STARTED = "STARTED";
+       private final static String FINISHED = "FINISHED";
+
+
+       @RabbitListener(queues = "train")
+       public void receiveEvent(TrainTaskEvent event) {
+               ProgressState progressState = setProgressState(event);
+               progressPresenter.present(progressState);
+               System.out.println(
+                               "Train task id: " + event.getId() + " ; method: " + event.getMethod() + " ; status: " + event.getStatus());
+
+       }
+
+       private ProgressState setProgressState(TrainTaskEvent event) {
+               ProgressName progressName = null;
+               ProgressStatus progressStatus = null;
+               switch (event.getMethod()) {
+               case ENCODING:
+                       progressName = ProgressName.ENCODING;
+                       break;
+               case FEATURE_ENGINEERING:
+                       progressName = ProgressName.FEATURE_ENGINEERING;
+                       break;
+               case SAMPLING:
+                       progressName = ProgressName.SAMPLING;
+                       break;
+               case FITTING:
+                       progressName = ProgressName.FITTING;
+                       break;
+               case ESTIMATOR_PERSISTING:
+                       progressName = ProgressName.ESTIMATOR_PERSISTING;
+                       break;
+               case METRIC_PERSISTING:
+                       progressName = ProgressName.METRIC_PERSISTING;
+                       break;
+               }
+               switch (event.getStatus()) {
+               case PREVIOUSLY_DONE:
+                       progressStatus = ProgressStatus.PREVIOUSLY_DONE;
+                       break;
+               case STARTED:
+                       progressStatus = ProgressStatus.STARTED;
+                       break;
+               case FINISHED:
+                       progressStatus = ProgressStatus.FINISHED;
+                       break;
+               }
+               ProgressState progressState = new ProgressState(progressName, progressStatus);
+               return progressState;
+       }
+}
+
+
+
+
+
+
+
+
+
diff --git a/TrainCli/src/main/java/hu/user/traincli/scheduled/JobScheduleCreator.java b/TrainCli/src/main/java/hu/user/traincli/scheduled/JobScheduleCreator.java
new file mode 100644 (file)
index 0000000..245e5c9
--- /dev/null
@@ -0,0 +1,45 @@
+package hu.user.traincli.scheduled;
+
+import java.text.ParseException;
+import java.util.Date;
+
+import org.quartz.CronTrigger;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.SimpleTrigger;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
+import org.springframework.scheduling.quartz.JobDetailFactoryBean;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
+import org.springframework.stereotype.Component;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+public class JobScheduleCreator {
+
+       public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable, ApplicationContext context,
+                       String jobName, JobDataMap jobDataMap ) {
+               JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
+               factoryBean.setJobClass(jobClass);
+               factoryBean.setDurability(isDurable);
+               factoryBean.setApplicationContext(context);
+               factoryBean.setName(jobName);
+               factoryBean.setJobDataMap(jobDataMap);
+               factoryBean.afterPropertiesSet();
+               return factoryBean.getObject();
+       }
+
+       public SimpleTrigger createSimpleTrigger(String triggerName, Date startTime, Long repeatTime) {
+               SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
+               factoryBean.setName(triggerName);
+               factoryBean.setStartTime(startTime);
+               factoryBean.setRepeatInterval(repeatTime);
+               factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
+               factoryBean.afterPropertiesSet();
+               SimpleTrigger simpleTrigger= factoryBean.getObject();
+               return simpleTrigger;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/scheduled/job/ShowProgressJob.java b/TrainCli/src/main/java/hu/user/traincli/scheduled/job/ShowProgressJob.java
new file mode 100644 (file)
index 0000000..fc30911
--- /dev/null
@@ -0,0 +1,41 @@
+package hu.user.traincli.scheduled.job;
+
+import java.util.List;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.PersistJobDataAfterExecution;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+@PersistJobDataAfterExecution
+@DisallowConcurrentExecution
+public class ShowProgressJob extends QuartzJobBean {
+
+       public ShowProgressJob() {
+       }
+
+       public static final String EXECUTION_COUNT = "count";
+       public static final String FIRST_RUN = "first_run";
+
+       private List<String> characters = List.of("|","/", "-", "\\");
+
+       @Override
+       public void executeInternal(JobExecutionContext context) throws JobExecutionException {
+               JobDataMap data = context.getJobDetail().getJobDataMap();
+               int count = data.getInt(EXECUTION_COUNT);
+               boolean firstRun = data.getBoolean(FIRST_RUN);
+               if (!firstRun) {
+                       System.out.print("\b");
+               }
+               int orderNumber = count % 4;
+               String actualString = characters.get(orderNumber);
+               System.out.print(actualString);
+               count++;
+               firstRun = false;
+               data.put(EXECUTION_COUNT, count);
+               data.put(FIRST_RUN, firstRun);
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/scheduled/job/TrainLivenessChecker.java b/TrainCli/src/main/java/hu/user/traincli/scheduled/job/TrainLivenessChecker.java
new file mode 100644 (file)
index 0000000..a4eba26
--- /dev/null
@@ -0,0 +1,34 @@
+package hu.user.traincli.scheduled.job;
+
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+import org.springframework.stereotype.Service;
+
+import hu.user.traincli.event.TrainLiveCheckEvent;
+import hu.user.traincli.service.TrainClient;
+import hu.user.traincli.status.TrainStatus;
+
+@Service
+public class TrainLivenessChecker extends QuartzJobBean {
+
+       @Autowired private TrainClient trainClient;
+
+       @Autowired private ApplicationEventPublisher applicationEventPublisher;
+
+       @Override
+       protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
+               checkIfTrainIsWorking();
+       }
+
+       private void checkIfTrainIsWorking() {
+               TrainStatus trainStatus = trainClient.testLiveness();
+               if (trainStatus == TrainStatus.NOT_ACCESSIBLE) {
+                       applicationEventPublisher.publishEvent(new TrainLiveCheckEvent("Train", TrainStatus.NOT_ACCESSIBLE));
+               } else if (trainStatus == TrainStatus.RESPONSE_TIMEOUT) {
+                       applicationEventPublisher.publishEvent(new TrainLiveCheckEvent("Train", TrainStatus.RESPONSE_TIMEOUT));
+               }
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/scheduled/jobinfo/SchedulerJobInfo.java b/TrainCli/src/main/java/hu/user/traincli/scheduled/jobinfo/SchedulerJobInfo.java
new file mode 100644 (file)
index 0000000..841c9b5
--- /dev/null
@@ -0,0 +1,15 @@
+package hu.user.traincli.scheduled.jobinfo;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@ToString
+@Getter
+@Setter
+public class SchedulerJobInfo {
+    private String jobName;
+    private String jobGroup;
+    private String jobClass;
+    private Long repeatTime;
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/scheduled/liveness/LivenessCheck.java b/TrainCli/src/main/java/hu/user/traincli/scheduled/liveness/LivenessCheck.java
new file mode 100644 (file)
index 0000000..6a60bfd
--- /dev/null
@@ -0,0 +1,36 @@
+package hu.user.traincli.scheduled.liveness;
+
+import java.util.List;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.PersistJobDataAfterExecution;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+import org.springframework.stereotype.Service;
+
+import hu.user.traincli.service.TrainClient;
+import hu.user.traincli.status.TrainStatus;
+
+@PersistJobDataAfterExecution
+@DisallowConcurrentExecution
+@Service
+public class LivenessCheck extends QuartzJobBean {
+
+       @Autowired
+       private TrainClient trainClient;
+       public LivenessCheck() {
+       }
+
+       private List<String> characters = List.of("|","/", "-", "\\");
+
+       @Override
+       public void executeInternal(JobExecutionContext context) throws JobExecutionException {
+               TrainStatus trainStatus = trainClient.testLiveness();
+               if (trainStatus != TrainStatus.OK) {
+
+               }
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/scheduled/progress/ShowProgress.java b/TrainCli/src/main/java/hu/user/traincli/scheduled/progress/ShowProgress.java
new file mode 100644 (file)
index 0000000..3242cea
--- /dev/null
@@ -0,0 +1,41 @@
+package hu.user.traincli.scheduled.progress;
+
+import java.util.List;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.PersistJobDataAfterExecution;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+@PersistJobDataAfterExecution
+@DisallowConcurrentExecution
+public class ShowProgress extends QuartzJobBean {
+
+       public ShowProgress() {
+       }
+
+       public static final String EXECUTION_COUNT = "count";
+       public static final String FIRST_RUN = "first_run";
+
+       private List<String> characters = List.of("|","/", "-", "\\");
+
+       @Override
+       public void executeInternal(JobExecutionContext context) throws JobExecutionException {
+               JobDataMap data = context.getJobDetail().getJobDataMap();
+               int count = data.getInt(EXECUTION_COUNT);
+               boolean firstRun = data.getBoolean(FIRST_RUN);
+               if (!firstRun) {
+                       System.out.print("\b");
+               }
+               int orderNumber = count % 4;
+               String actualString = characters.get(orderNumber);
+               System.out.print(actualString);
+               count++;
+               firstRun = false;
+               data.put(EXECUTION_COUNT, count);
+               data.put(FIRST_RUN, firstRun);
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/service/JsonConverter.java b/TrainCli/src/main/java/hu/user/traincli/service/JsonConverter.java
new file mode 100644 (file)
index 0000000..0a17427
--- /dev/null
@@ -0,0 +1,36 @@
+package hu.user.traincli.service;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JsonConverter {
+
+       public static <T> T jsonToJava(String filePath, String fileName, Class<T> classType)  {
+
+               T t = null;
+               String inputString;
+               Path path = Path.of(filePath + "/" + fileName);
+
+               try {
+                       inputString = Files.readString(path);
+               } catch (IOException e) {
+                       throw new RuntimeException("JSON file not found or can not be loaded");
+               }
+               ObjectMapper mapper = new ObjectMapper();
+                       mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+               try {
+                       t = mapper.readValue(inputString, classType);
+               } catch (JsonProcessingException e) {
+                       throw new RuntimeException("JSON file can not be deserialized");
+               }
+               return t;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/service/ProgressPresenter.java b/TrainCli/src/main/java/hu/user/traincli/service/ProgressPresenter.java
new file mode 100644 (file)
index 0000000..c6356cf
--- /dev/null
@@ -0,0 +1,55 @@
+package hu.user.traincli.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Service;
+
+import hu.user.traincli.event.TrainLiveCheckEvent;
+import hu.user.traincli.status.TrainStatus;
+import hu.user.traincli.train.ProgressName;
+import hu.user.traincli.train.ProgressState;
+
+@Service
+public class ProgressPresenter implements ApplicationListener<TrainLiveCheckEvent> {
+
+       @Autowired
+       private SchedulerJobService schedulerJobService;
+
+       public void present(ProgressState progressState) {
+
+               switch (progressState.getProgressStatus()) {
+               case STARTED:
+                       if (progressState.getProgressName().equals(ProgressName.ENCODING)) {
+                               schedulerJobService.startScheduler();
+                       } else {
+                               schedulerJobService.resumeShowProgress();
+                       }
+                       System.out.println(progressState.toString());
+                       System.out.print("In Progress: ");
+                       break;
+               case FINISHED:
+//                     if (progressState.getProgressName().equals(ProgressName.METRIC_PERSISTING)) {
+//                             schedulerJobService.stopScheduler();
+//                     }
+                       schedulerJobService.pauseShowProgress();
+                       System.out.println();
+                       System.out.println(progressState.toString());
+                       break;
+               case PREVIOUSLY_DONE:
+                       System.out.println(progressState.toString());
+                       break;
+               }
+       }
+       @Override
+       public void onApplicationEvent(TrainLiveCheckEvent event) {
+               if (event.getTrainStatus().equals(TrainStatus.NOT_ACCESSIBLE)) {
+                       System.out.println("Train module isn't responding");
+                       schedulerJobService.pauseShowProgress();
+                       schedulerJobService.pauseTrainLivenessCheck();
+               } else if (event.getTrainStatus().equals(TrainStatus.RESPONSE_TIMEOUT)) {
+                       System.out.println("Train module isn't responding in time");
+                       schedulerJobService.pauseShowProgress();
+                       schedulerJobService.pauseTrainLivenessCheck();
+               }
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/service/SchedulerJobService.java b/TrainCli/src/main/java/hu/user/traincli/service/SchedulerJobService.java
new file mode 100644 (file)
index 0000000..49c2af8
--- /dev/null
@@ -0,0 +1,178 @@
+package hu.user.traincli.service;
+
+import static hu.user.traincli.scheduled.job.ShowProgressJob.EXECUTION_COUNT;
+import static hu.user.traincli.scheduled.job.ShowProgressJob.FIRST_RUN;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+
+import org.quartz.JobBuilder;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerMetaData;
+import org.quartz.SimpleTrigger;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import hu.user.traincli.scheduled.JobScheduleCreator;
+import hu.user.traincli.scheduled.job.ShowProgressJob;
+import hu.user.traincli.scheduled.job.TrainLivenessChecker;
+import hu.user.traincli.scheduled.jobinfo.SchedulerJobInfo;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Transactional
+@Service
+public class SchedulerJobService {
+
+       @Value("${showProgressRepeatTime}")
+       private long showProgressRepeatTime;
+       @Value("${trainLivenessRepeatTime}")
+       private long trainLivenessRepeatTime;
+
+       private static final String SHOW_PROGRESS_JOB = "showProgressJob";
+       private static final String SHOW_PROGRESS_TRIGGER = "showProgressTrigger";
+       private static final String TRAIN_LIVENESS_CHECKER_JOB = "trainLivenessCheckerJob";
+       private static final String TRAIN_LIVENESS_CHECKER_TRIGGER = "trainLivenessCheckerTrigger";
+       @Autowired private Scheduler scheduler;
+
+       @Autowired private SchedulerFactoryBean schedulerFactoryBean;
+
+       @Autowired private ApplicationContext context;
+
+       @Autowired private JobScheduleCreator scheduleCreator;
+
+       private JobDetail showProgressJobDetail;
+       private JobDetail trainLivenessCheckerJobDetail;
+
+       private SimpleTrigger showProgressTrigger;
+       private SimpleTrigger trainLivenessTrigger;
+
+       @PostConstruct
+       private void initialize() {
+               JobDataMap showProgressDataMap = new JobDataMap(Map.of(EXECUTION_COUNT, 0, FIRST_RUN, true));
+               showProgressJobDetail =
+                               scheduleCreator.createJob(ShowProgressJob.class, true, context, SHOW_PROGRESS_JOB, showProgressDataMap);
+               trainLivenessCheckerJobDetail =
+                               scheduleCreator.createJob(TrainLivenessChecker.class, true, context, TRAIN_LIVENESS_CHECKER_JOB,
+                                               new JobDataMap());
+               Date now = Date.from(Instant.now());
+               showProgressTrigger = scheduleCreator.createSimpleTrigger(SHOW_PROGRESS_TRIGGER, now,
+                               showProgressRepeatTime);
+               trainLivenessTrigger = scheduleCreator.createSimpleTrigger(TRAIN_LIVENESS_CHECKER_TRIGGER, now,
+                               trainLivenessRepeatTime);
+               scheduler = schedulerFactoryBean.getScheduler();
+               try {
+                       scheduler.scheduleJob(showProgressJobDetail, showProgressTrigger);
+                       scheduler.scheduleJob(trainLivenessCheckerJobDetail, trainLivenessTrigger);
+               } catch (SchedulerException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public void startScheduler() {
+               try {
+                       scheduler.start();
+               } catch (SchedulerException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public void pauseShowProgress() {
+               try {
+                       scheduler.pauseTrigger(showProgressTrigger.getKey());
+               } catch (SchedulerException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public void pauseTrainLivenessCheck() {
+               try {
+                       scheduler.pauseTrigger(trainLivenessTrigger.getKey());
+               } catch (SchedulerException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public void resumeShowProgress() {
+               try {
+                       scheduler.resumeTrigger(showProgressTrigger.getKey());
+               } catch (SchedulerException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public void resumeTrainLivenessCheck() {
+               try {
+                       scheduler.resumeTrigger(trainLivenessTrigger.getKey());
+               } catch (SchedulerException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public void stopScheduler() {
+               try {
+                       scheduler.shutdown();
+               } catch (SchedulerException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+
+//     public boolean pauseJob(SchedulerJobInfo jobInfo) {
+//             try {
+//                     SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName());
+//                     getJobInfo.setJobStatus("PAUSED");
+//                     schedulerRepository.save(getJobInfo);
+//                     schedulerFactoryBean.getScheduler().pauseJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup()));
+//                     log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " paused.");
+//                     return true;
+//             } catch (SchedulerException e) {
+//                     log.error("Failed to pause job - {}", jobInfo.getJobName(), e);
+//                     return false;
+//             }
+//     }
+
+//     public boolean resumeJob(SchedulerJobInfo jobInfo) {
+//             try {
+//                     SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName());
+//                     getJobInfo.setJobStatus("RESUMED");
+//                     schedulerRepository.save(getJobInfo);
+//                     schedulerFactoryBean.getScheduler().resumeJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup()));
+//                     log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " resumed.");
+//                     return true;
+//             } catch (SchedulerException e) {
+//                     log.error("Failed to resume job - {}", jobInfo.getJobName(), e);
+//                     return false;
+//             }
+//     }
+
+//     public boolean startJobNow(SchedulerJobInfo jobInfo) {
+//             try {
+//                     SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName());
+//                     getJobInfo.setJobStatus("SCHEDULED & STARTED");
+//                     schedulerRepository.save(getJobInfo);
+//                     schedulerFactoryBean.getScheduler().triggerJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup()));
+//                     log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " scheduled and started now.");
+//                     return true;
+//             } catch (SchedulerException e) {
+//                     log.error("Failed to start new job - {}", jobInfo.getJobName(), e);
+//                     return false;
+//             }
+//     }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/service/TrainClient.java b/TrainCli/src/main/java/hu/user/traincli/service/TrainClient.java
new file mode 100644 (file)
index 0000000..7d316b4
--- /dev/null
@@ -0,0 +1,227 @@
+package hu.user.traincli.service;
+
+import java.lang.reflect.ParameterizedType;
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import hu.user.traincli.ParametersExistYetException;
+import hu.user.traincli.dto.CommonResponse;
+import hu.user.traincli.dto.CommonTrainParameters;
+import hu.user.traincli.dto.EncodingAndFeatureEngineeringParameters;
+import hu.user.traincli.dto.EstimatorIdentifiers;
+import hu.user.traincli.dto.FitParameters;
+import hu.user.traincli.dto.LivenessDto;
+import hu.user.traincli.dto.TableProperties;
+import hu.user.traincli.dto.TrainTaskParameters;
+import hu.user.traincli.status.TrainStatus;
+import lombok.Getter;
+import lombok.Setter;
+
+@Service
+@Getter
+@Setter
+public class
+TrainClient {
+
+       private static final String OK = "OK";
+       private static final String ENCODING_AND_FEATURE_ENGINEERING_PATH = "/encoding_and_feature_engineering_plan";
+       private static final String TRAIN_TASK_PATH = "/train_task";
+       private static final String FIT_PATH = "/fit";
+       private static final String SCHEMA_PATH = "/schema";
+       private static final String TABLE_PATH = "/table";
+       private static final String RECORD_PATH = "/record";
+       private static final String METRICS_PATH = "/metrics";
+       private static final String CPU_PATH = "/cpu";
+       private static final String ESTIMATOR_PATH = "/estimator";
+       private static final String LIVENESS_PATH = "/liveness";
+       private static final String TABLE_INFO_PATH="/table_info";
+
+
+
+       @Value("${trainUrl}") String trainUrl;
+       @Autowired private RestTemplate restTemplate;
+//     private String trainUrl;
+
+       public TrainStatus testLiveness() {
+               TrainStatus trainStatus = null;
+               String url = trainUrl + LIVENESS_PATH;
+               try {
+                       LivenessDto livenessDto = restTemplate.getForEntity(url, LivenessDto.class).getBody();
+                       if (livenessDto.getResponse().equals(OK)) {
+                               trainStatus = TrainStatus.OK;
+                       }
+               } catch (ResourceAccessException exception) {
+                       if (exception.getCause() instanceof ConnectException) {
+                               trainStatus = TrainStatus.NOT_ACCESSIBLE;
+                       }
+                       if (exception.getCause() instanceof SocketTimeoutException) {
+                               //TO-DO logging
+                               trainStatus = TrainStatus.RESPONSE_TIMEOUT;
+                       }
+               }
+               return trainStatus;
+       }
+
+       public int getCpuCoreNumber() {
+               String url = trainUrl + CPU_PATH;
+               int coreNumber = restTemplate.getForEntity(url, Integer.class).getBody();
+               return coreNumber;
+       }
+
+       public String[] getSchemas() {
+               String url = trainUrl + SCHEMA_PATH;
+               String[] schemas = restTemplate.getForEntity(url, String[].class).getBody();
+               return schemas;
+       }
+
+       public String[] getTables(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TABLE_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               String[] tables = restTemplate.getForEntity(urlTemplate, String[].class, params).getBody();
+               return tables;
+       }
+
+       public TableProperties getTableProperties(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TABLE_INFO_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<TableProperties> tableProperties =
+                               restTemplate.getForEntity(urlTemplate, TableProperties.class, params);
+               TableProperties result = tableProperties.getBody();
+               return result;
+       }
+
+       public String getRecords(Map<String, String> queryParamValueByName) {
+               String url = trainUrl +RECORD_PATH ;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> tableProperties = restTemplate.getForEntity(urlTemplate, String.class, params);
+               String result = tableProperties.getBody();
+               return result;
+       }
+
+       public String getEncodingAndFeatureEngineeringParameters(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + ENCODING_AND_FEATURE_ENGINEERING_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> tableProperties = restTemplate.getForEntity(urlTemplate, String.class, params);
+               String result = tableProperties.getBody();
+               return result;
+       }
+
+       public String getTrainTask(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TRAIN_TASK_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> tableProperties = restTemplate.getForEntity(urlTemplate, String.class, params);
+               String result = tableProperties.getBody();
+               return result;
+       }
+
+//     public EncodingAndFeatureEngineeringParameters getEncodingAndFeatureEngineeringParameters() {
+//
+//     }
+
+       public Map<String, String> getFeatureSelectors() {
+               return useCommmonQuery("/available_feature_selector");
+       }
+
+       public Map<String, String> getSamplers() {
+               return useCommmonQuery("/available_sampler");
+       }
+
+       public Map<String, String> getScalers() {
+               return useCommmonQuery("/available_scaler");
+       }
+
+       public Map<String, String> getModels() {
+               return useCommmonQuery("/available_model");
+       }
+
+       public Map<String, String> useCommmonQuery(String path) {
+               String url = trainUrl + path;
+               Map<String,String> parameters = restTemplate.getForEntity(url, Map.class).getBody();
+               return parameters;
+       }
+
+       public EstimatorIdentifiers[] getEstimatorIdentifiers() {
+               String url = trainUrl + "/estimator";
+               EstimatorIdentifiers[] estimatorIdentifiers = restTemplate.getForEntity(url, EstimatorIdentifiers[].class).getBody();
+               return estimatorIdentifiers;
+       }
+
+       public List<Map<String, Double>> getMetrics(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + METRICS_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+
+               ParameterizedTypeReference<List<Map<String,Double>>> typeReference= new ParameterizedTypeReference<>() {};
+               List<Map<String, Double>> metrics = exchangeAsList(urlTemplate, typeReference, params);
+               return metrics;
+       }
+
+       public String addEncodingAndFeatureEngineeringParameters(EncodingAndFeatureEngineeringParameters parameters) {
+               return addParameters(trainUrl,parameters, ENCODING_AND_FEATURE_ENGINEERING_PATH);
+       }
+
+       public String addTrainTask(TrainTaskParameters trainTaskParameters) {
+               return addParameters(trainUrl,trainTaskParameters, TRAIN_TASK_PATH);
+       }
+
+       public void fit(FitParameters parameters) {
+               addParameters(trainUrl,parameters, FIT_PATH);
+       }
+
+       public <T> String addParameters(String trainUrl,T parameters, String path) {
+               String url = trainUrl + path;
+               try {
+                       ResponseEntity<String> responseFromTrainModule =
+                                       restTemplate.postForEntity(url, parameters, String .class);
+                       return responseFromTrainModule.getBody();
+               } catch (HttpClientErrorException httpClientErrorException) {
+                       String errorMessage = httpClientErrorException.getResponseBodyAsString();
+                       throw new ParametersExistYetException(errorMessage);
+               }
+       }
+
+       private  <T> List<T> exchangeAsList(String uri, ParameterizedTypeReference<List<T>> responseType, Map<String,String> uriVariables) {
+               return restTemplate.exchange(uri, HttpMethod.GET, null, responseType,uriVariables).getBody();
+       }
+
+}
+
diff --git a/TrainCli/src/main/java/hu/user/traincli/service/UrlBuilder.java b/TrainCli/src/main/java/hu/user/traincli/service/UrlBuilder.java
new file mode 100644 (file)
index 0000000..4abee1a
--- /dev/null
@@ -0,0 +1,50 @@
+package hu.user.traincli.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class UrlBuilder {
+
+       private String url;
+       private Map<String, String> queryParams = new HashMap<>();
+
+       public String getUrl() {
+               return url;
+       }
+
+       public Map<String, String> getQueryParams() {
+               return queryParams;
+       }
+
+       public void build(String url, Map<String, String> queryValueByParamName) {
+               List<String> paramNames = new ArrayList<>();
+               List<String> paramValues = new ArrayList<>();
+               for (var entry : queryValueByParamName.entrySet()) {
+                       paramNames.add(entry.getKey());
+                       paramValues.add(entry.getValue());
+               }
+               buildUrl(url, paramNames);
+               fillQueryParams(paramNames, paramValues);
+       }
+
+       private void buildUrl(String url, List<String> queryParamNames) {
+               String urlTemplate;
+               UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url);
+               for (String queryParamName : queryParamNames) {
+                       uriComponentsBuilder = uriComponentsBuilder.queryParam(queryParamName, "{" + queryParamName + "}");
+               }
+               this.url = uriComponentsBuilder.encode().toUriString();
+       }
+
+       private void fillQueryParams(List<String> paramNames, List<String> paramValuee) {
+               Map<String, String> params = new HashMap<>();
+               for (int i = 0; i < paramNames.size(); i++) {
+                       params.put(paramNames.get(i), paramValuee.get(i));
+               }
+               this.queryParams = params;
+       }
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/status/TrainStatus.java b/TrainCli/src/main/java/hu/user/traincli/status/TrainStatus.java
new file mode 100644 (file)
index 0000000..acbedf6
--- /dev/null
@@ -0,0 +1,7 @@
+package hu.user.traincli.status;
+
+public enum TrainStatus {
+       OK,
+       NOT_ACCESSIBLE,
+       RESPONSE_TIMEOUT
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/train/ProgressName.java b/TrainCli/src/main/java/hu/user/traincli/train/ProgressName.java
new file mode 100644 (file)
index 0000000..f8daa63
--- /dev/null
@@ -0,0 +1,11 @@
+package hu.user.traincli.train;
+
+public enum ProgressName {
+
+       ENCODING,
+       FEATURE_ENGINEERING,
+       SAMPLING,
+       FITTING,
+       ESTIMATOR_PERSISTING,
+       METRIC_PERSISTING
+}
diff --git a/TrainCli/src/main/java/hu/user/traincli/train/ProgressState.java b/TrainCli/src/main/java/hu/user/traincli/train/ProgressState.java
new file mode 100644 (file)
index 0000000..a5db419
--- /dev/null
@@ -0,0 +1,18 @@
+package hu.user.traincli.train;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class ProgressState {
+
+       private ProgressName progressName;
+       private ProgressStatus progressStatus;
+
+       @Override
+       public String toString() {
+               return progressName + " " + progressStatus;
+       }
+}
+
diff --git a/TrainCli/src/main/java/hu/user/traincli/train/ProgressStatus.java b/TrainCli/src/main/java/hu/user/traincli/train/ProgressStatus.java
new file mode 100644 (file)
index 0000000..61b0121
--- /dev/null
@@ -0,0 +1,9 @@
+package hu.user.traincli.train;
+
+public enum ProgressStatus {
+
+       READY,
+       PREVIOUSLY_DONE,
+       STARTED,
+       FINISHED
+}
diff --git a/TrainCli/src/main/resources/application.properties b/TrainCli/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..f5ebeac
--- /dev/null
@@ -0,0 +1,6 @@
+server.port=8086
+trainUrl=http://localhost:8085
+#trainUrl=http://mkigui:8085
+showProgressRepeatTime=500
+trainLivenessRepeatTime=100
+
diff --git a/TrainCli/src/main/resources/task.json b/TrainCli/src/main/resources/task.json
new file mode 100644 (file)
index 0000000..985a864
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "planned_encoding_and_feature_engineering_id":2,
+  "feature_selector_code":1,
+  "sampler_code":1,
+  "scaler_code":1,
+  "model_code":8,
+  "test_size": 0.2
+}
\ No newline at end of file
diff --git a/TrainCli/src/test/java/hu/user/traincli/TrainCliApplicationTests.java b/TrainCli/src/test/java/hu/user/traincli/TrainCliApplicationTests.java
new file mode 100644 (file)
index 0000000..f4ade86
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.traincli;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class TrainCliApplicationTests {
+
+       @Test
+       void contextLoads() {
+       }
+
+}
diff --git a/TrainManager/Dockerfile b/TrainManager/Dockerfile
new file mode 100644 (file)
index 0000000..3708e50
--- /dev/null
@@ -0,0 +1,4 @@
+FROM adoptopenjdk:14-jre-hotspot
+WORKDIR /opt/app
+COPY target/*.jar trainmanager.jar
+CMD ["java","-jar","trainmanager.jar"]
\ No newline at end of file
diff --git a/TrainManager/pom.xml b/TrainManager/pom.xml
new file mode 100644 (file)
index 0000000..e34a1c1
--- /dev/null
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.5.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>hu.user</groupId>
+    <artifactId>TrainManager</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>TrainManager</name>
+    <description>TrainManager</description>
+    <properties>
+        <java.version>11</java.version>
+        <zkspringboot.version>2.5.3</zkspringboot.version>
+        <springboot.version>2.5.3</springboot.version>
+    </properties>
+
+    <repositories> <!--add-->
+        <repository>
+            <id>spring-snapshots</id>
+            <name>Spring Snapshots</name>
+            <url>https://repo.spring.io/snapshot</url>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>spring-milestones</id>
+            <name>Spring Milestones</name>
+            <url>https://repo.spring.io/milestone</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>ZK CE</id>
+            <name>ZK CE Repository</name>
+            <url>http://mavensync.zkoss.org/maven2</url>
+        </repository>
+        <repository>
+            <id>ZK EVAL</id>
+            <name>ZK Evaluation Repository</name>
+            <url>http://mavensync.zkoss.org/eval</url>
+        </repository>
+    </repositories>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <version>${springboot.version}</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency> <!--add-->
+            <groupId>org.zkoss.zkspringboot</groupId>
+            <artifactId>zkspringboot-starter</artifactId>
+            <type>pom</type>
+            <version>${zkspringboot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.zkoss.zk</groupId>
+            <artifactId>zkplus</artifactId>
+            <version>9.6.0-Eval</version>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.28</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.opencsv/opencsv -->
+        <dependency>
+            <groupId>com.opencsv</groupId>
+            <artifactId>opencsv</artifactId>
+            <version>5.7.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/TrainManagerApplication.java b/TrainManager/src/main/java/hu/user/trainmanager/TrainManagerApplication.java
new file mode 100644 (file)
index 0000000..fd17a23
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.trainmanager;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class TrainManagerApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(TrainManagerApplication.class, args);
+    }
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/component/TrainProgressStateHolder.java b/TrainManager/src/main/java/hu/user/trainmanager/component/TrainProgressStateHolder.java
new file mode 100644 (file)
index 0000000..ff0636d
--- /dev/null
@@ -0,0 +1,239 @@
+package hu.user.trainmanager.component;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.stereotype.Component;
+
+import hu.user.trainmanager.dto.ConfusionMatrixDto;
+import hu.user.trainmanager.dto.FitParameters;
+import hu.user.trainmanager.dto.TrainTaskParameters;
+import hu.user.trainmanager.dto.progress.TrainProgressScreenProperties;
+import hu.user.trainmanager.dto.progress.TrainProgressState;
+import hu.user.trainmanager.event.TrainTaskEvent;
+import hu.user.trainmanager.event.TrainTaskMethodState;
+import hu.user.trainmanager.service.TrainClient;
+import hu.user.trainmanager.status.TrainTaskState;
+import lombok.Getter;
+import lombok.Setter;
+
+@Component
+@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Getter
+@Setter
+public class TrainProgressStateHolder {
+
+       private static final String TRAIN_TASK_ID = "train_task_id";
+       @Autowired private TrainClient trainClient;
+
+       public TrainProgressStateHolder(TrainClient trainClient) {
+               this.trainClient = trainClient;
+       }
+
+//     private List<EstimatorAndTrainTaskIdentifiers> estimatorAndTrainTaskIdentifiers = new ArrayList<>();
+       private List<TrainProgressState> trainProgressStates = new ArrayList<>();
+       private Map<Integer, TrainTaskParameters> trainTaskParametersById = new HashMap<>();
+
+       private List<String> trainWorkCollection = new ArrayList<>();
+
+       private TrainProgressScreenProperties trainProgressScreenProperties;
+
+       private boolean trainInProgress;
+
+       private int usedCpuCoreNumber;
+
+       private int availableCpuCoreNumber;
+
+       private boolean stopped;
+
+       @PostConstruct
+       public void init() {
+               availableCpuCoreNumber = trainClient.getCpuCoreNumber();
+               trainTaskParametersById = trainClient.getTrainTask(Collections.emptyMap());
+//             loadEstimatorAndTrainTaskIdentifiers();
+//             List<Integer> fittedTrainTaskIds =
+//                             estimatorAndTrainTaskIdentifiers.stream().map(e -> e.getTrainTaskId()).collect(Collectors.toList());
+               // @formatter:off
+               trainProgressStates = trainTaskParametersById
+                               .entrySet()
+                               .stream()
+                               .filter(e->e.getValue().getTrainTaskState().equals(TrainTaskState.STORED))
+                               .map(e->new TrainProgressState(e.getKey(), e.getValue().getTrainTaskName(),TrainTaskMethodState.READY, TrainTaskMethodState.READY,
+                                               TrainTaskMethodState.READY, TrainTaskMethodState.READY, TrainTaskMethodState.READY,
+                                               TrainTaskMethodState.READY ,TrainTaskState.STORED,false))
+                               .collect(Collectors.toList());
+               // @formatter:on
+               int allTrainTaskNumber = trainTaskParametersById.keySet().size();
+               int finishedTrainTaskNumber =
+                               (int) trainTaskParametersById.entrySet().stream().filter(e->e.getValue().getTrainTaskState().equals(TrainTaskState.DONE)).count();
+               trainProgressScreenProperties =
+                               new TrainProgressScreenProperties(allTrainTaskNumber, Optional.of(finishedTrainTaskNumber),
+                                               Optional.empty(), Optional.empty(), true, true, false);
+       }
+
+//     public void loadEstimatorAndTrainTaskIdentifiers() {
+//             estimatorAndTrainTaskIdentifiers = trainClient.getEstimatorIdentifiers();
+//     }
+
+       public ConfusionMatrixDto getConfusionMatrixElements(int trainTaskId) {
+               return trainClient.getConfusionMatrixElements(Map.of(TRAIN_TASK_ID, String.valueOf(trainTaskId)));
+       }
+
+       public TrainProgressScreenProperties changeProgressStatus(TrainTaskEvent event) {
+               int currentTrainTaskId = event.getTrainTaskId();
+               Optional<TrainProgressState> optionalTrainProgress =
+                               trainProgressStates.stream().filter(e -> e.getId() == currentTrainTaskId).findFirst();
+               if (optionalTrainProgress.isPresent()) {
+                       TrainProgressState currentTrainProgressState = optionalTrainProgress.get();
+                       TrainTaskMethodState trainTaskMethodState = event.getMethodState();
+                       switch (event.getMethodName()) {
+                       case ENCODING:
+                               currentTrainProgressState.setEncodingState(trainTaskMethodState);
+                               currentTrainProgressState.setTrainTaskState(TrainTaskState.IN_PROGRESS);
+                               System.out.println("Changed encoding state");
+                               break;
+                       case FEATURE_ENGINEERING:
+                               currentTrainProgressState.setFeatureEngineeringState(trainTaskMethodState);
+                               System.out.println("Changed feature engineering state");
+                               break;
+                       case SAMPLING:
+                               currentTrainProgressState.setSamplingProcessState(trainTaskMethodState);
+                               System.out.println("Changed sampling state");
+                               break;
+                       case FITTING:
+                               currentTrainProgressState.setFitProcessState(trainTaskMethodState);
+                               System.out.println("Changed fitting state");
+                               break;
+                       case ESTIMATOR_PERSISTING:
+                               currentTrainProgressState.setEstimatorPersistingState(trainTaskMethodState);
+                               break;
+                       case METRIC_PERSISTING:
+                               currentTrainProgressState.setMetricsPersistingState(trainTaskMethodState);
+                               currentTrainProgressState.setTrainTaskState(TrainTaskState.DONE);
+                               incrementFinishedTrainTaskNumber();
+                               eraseCurrentProgressedTrainTaskId();
+                               break;
+                       }
+               }
+               return trainProgressScreenProperties;
+       }
+
+       private void eraseCurrentProgressedTrainTaskId() {
+               trainProgressScreenProperties.setCurrentProgressedTrainTaskId(Optional.empty());
+       }
+
+       private void enableStopButton() {
+               trainProgressScreenProperties.setStopButtonDisabled(false);
+       }
+
+       private void incrementFinishedTrainTaskNumber() {
+               if (trainProgressScreenProperties.getFinishedTrainTaskNumber().isPresent()) {
+                       trainProgressScreenProperties.setFinishedTrainTaskNumber(
+                                       Optional.of(trainProgressScreenProperties.getFinishedTrainTaskNumber().get() + 1));
+               } else {
+                       trainProgressScreenProperties.setFinishedTrainTaskNumber(Optional.of(1));
+               }
+       }
+
+       public TrainProgressScreenProperties addTrainTaskToWorkCollection(String trainTaskid) {
+               if (!trainWorkCollection.contains(trainTaskid)) {
+                       trainWorkCollection.add(trainTaskid);
+                       if (trainProgressScreenProperties.getToBeDoneTrainTaskNumber().isPresent()) {
+                               trainProgressScreenProperties.setToBeDoneTrainTaskNumber(
+                                               Optional.of(trainProgressScreenProperties.getToBeDoneTrainTaskNumber().get() + 1));
+                       } else {
+                               trainProgressScreenProperties.setToBeDoneTrainTaskNumber(Optional.of(1));
+                       }
+               }
+               return trainProgressScreenProperties;
+       }
+
+       public TrainProgressScreenProperties removeTrainTaskFromWorkCollection(String trainTaskid) {
+               if (trainWorkCollection.contains(trainTaskid)) {
+                       trainWorkCollection.remove(trainTaskid);
+               }
+               if (trainWorkCollection.isEmpty()) {
+                       trainProgressScreenProperties.setToBeDoneTrainTaskNumber(Optional.empty());
+               } else {
+                       trainProgressScreenProperties.setToBeDoneTrainTaskNumber(Optional.of(trainWorkCollection.size()));
+               }
+               return trainProgressScreenProperties;
+       }
+
+       public TrainProgressScreenProperties startTrainProcess() {
+               if (!trainWorkCollection.isEmpty()) {
+                       trainInProgress = true;
+                       disableAllAddButton();
+                       startNextTrainTask();
+               }
+               return trainProgressScreenProperties;
+       }
+
+       private void disableAllAddButton() {
+               trainProgressStates.stream().forEach(e -> e.setAddButtonDisabled(true));
+       }
+
+       public TrainProgressScreenProperties startNextTrainTask() {
+               if (!trainWorkCollection.isEmpty() && !stopped) {
+                       int nextTrainTaskId = Integer.parseInt(trainWorkCollection.remove(0));
+                       trainProgressScreenProperties.setCurrentProgressedTrainTaskId(Optional.of(nextTrainTaskId));
+                       refreshToBeDoneTrainTaskNumber();
+                       startSingleTrainTask(nextTrainTaskId);
+               } else {
+                       disableStopButton();
+                       enableStartButton();
+                       enableTrainTaskRefreshButton();
+                       trainProgressScreenProperties.setCurrentProgressedTrainTaskId(Optional.empty());
+               }
+               return trainProgressScreenProperties;
+       }
+
+       private void disableStopButton() {
+               trainProgressScreenProperties.setStopButtonDisabled(true);
+       }
+
+       private void enableStartButton() {
+               trainProgressScreenProperties.setStartButtonDisabled(false);
+       }
+
+       private void enableTrainTaskRefreshButton() {
+               trainProgressScreenProperties.setTrainTaskRefreshButtonDisabled(false);
+       }
+
+       private void refreshToBeDoneTrainTaskNumber() {
+               if (trainWorkCollection.isEmpty()) {
+                       trainProgressScreenProperties.setToBeDoneTrainTaskNumber(Optional.empty());
+               } else {
+                       trainProgressScreenProperties.setToBeDoneTrainTaskNumber(Optional.of(trainWorkCollection.size()));
+               }
+       }
+
+       public void startSingleTrainTask(int trainTaskId) {
+               FitParameters fitParameters = new FitParameters(trainTaskId, usedCpuCoreNumber);
+               trainClient.fit(fitParameters);
+       }
+
+       public void refreshTrainTasks() {
+               init();
+       }
+
+       public void refreshAddButtonStates() {
+               for (TrainProgressState trainProgressState : trainProgressStates) {
+                       if (trainProgressState.getTrainTaskState().equals(TrainTaskState.DONE)) {
+                               trainProgressState.setAddButtonDisabled(true);
+                       } else {
+                               trainProgressState.setAddButtonDisabled(false);
+                       }
+               }
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/config/DevelopmentConfig.java b/TrainManager/src/main/java/hu/user/trainmanager/config/DevelopmentConfig.java
new file mode 100644 (file)
index 0000000..1e0621b
--- /dev/null
@@ -0,0 +1,36 @@
+package hu.user.trainmanager.config;\r
+\r
+import javax.annotation.PostConstruct;\r
+\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.Profile;\r
+import org.zkoss.lang.Library;\r
+import org.zkoss.zk.ui.WebApps;\r
+\r
+@Configuration\r
+@Profile("dev")\r
+public class DevelopmentConfig {\r
+       private static Logger logger = LoggerFactory.getLogger(DevelopmentConfig.class);\r
+\r
+       @PostConstruct\r
+       public void initDevelopmentProperties() throws Exception {\r
+               logger.info("**************************************************************");\r
+               logger.info("**** ZK-Springboot-Demo: development configuration active ****");\r
+               logger.info("**************************************************************");\r
+\r
+               //disable various caches to avoid server restarts\r
+               Library.setProperty("org.zkoss.zk.ZUML.cache", "false");\r
+               Library.setProperty("org.zkoss.zk.WPD.cache", "false");\r
+               Library.setProperty("org.zkoss.zk.WCS.cache", "false");\r
+               Library.setProperty("org.zkoss.web.classWebResource.cache", "false");\r
+               Library.setProperty("org.zkoss.util.label.cache", "false");\r
+\r
+               // enable non minified js\r
+               WebApps.getCurrent().getConfiguration().setDebugJS(true);\r
+\r
+               // enable for debugging MVVM commands and binding (very verbose)\r
+               Library.setProperty("org.zkoss.bind.DebuggerFactory.enable", "false");\r
+       }\r
+}\r
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/config/RabbitMqConfig.java b/TrainManager/src/main/java/hu/user/trainmanager/config/RabbitMqConfig.java
new file mode 100644 (file)
index 0000000..cb7c54c
--- /dev/null
@@ -0,0 +1,22 @@
+package hu.user.trainmanager.config;
+
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RabbitMqConfig {
+
+    @Bean
+    public Queue queue() {
+        return new Queue("train");
+    }
+
+    @Bean
+    public MessageConverter messageConverter() {
+        return new Jackson2JsonMessageConverter();
+    }
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/config/RestConfig.java b/TrainManager/src/main/java/hu/user/trainmanager/config/RestConfig.java
new file mode 100644 (file)
index 0000000..be67ffc
--- /dev/null
@@ -0,0 +1,21 @@
+package hu.user.trainmanager.config;
+
+import java.time.Duration;
+
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestConfig {
+
+    @Bean
+    public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
+        RestTemplate restTemplate = builder
+                .setReadTimeout(Duration.ofSeconds(7))
+                .build();
+        return restTemplate;
+
+    }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/controller/TrainTaskController.java b/TrainManager/src/main/java/hu/user/trainmanager/controller/TrainTaskController.java
new file mode 100644 (file)
index 0000000..6529ef3
--- /dev/null
@@ -0,0 +1,229 @@
+package hu.user.trainmanager.controller;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.zkoss.bind.BindUtils;
+import org.zkoss.bind.GlobalCommandEvent;
+import org.zkoss.bind.annotation.DefaultGlobalCommand;
+import org.zkoss.bind.annotation.GlobalCommand;
+import org.zkoss.zk.ui.Component;
+import org.zkoss.zk.ui.Desktop;
+import org.zkoss.zk.ui.Execution;
+import org.zkoss.zk.ui.Executions;
+import org.zkoss.zk.ui.event.Event;
+import org.zkoss.zk.ui.event.EventListener;
+import org.zkoss.zk.ui.event.EventQueues;
+import org.zkoss.zk.ui.event.ForwardEvent;
+import org.zkoss.zk.ui.select.SelectorComposer;
+import org.zkoss.zk.ui.select.annotation.Listen;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.Wire;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zul.Button;
+import org.zkoss.zul.Combobox;
+import org.zkoss.zul.ListModelList;
+import org.zkoss.zul.Listbox;
+import org.zkoss.zul.Listcell;
+import org.zkoss.zul.Listitem;
+import org.zkoss.zul.Textbox;
+
+import hu.user.trainmanager.component.TrainProgressStateHolder;
+import hu.user.trainmanager.dto.progress.TrainProgressScreenProperties;
+import hu.user.trainmanager.dto.progress.TrainProgressState;
+import hu.user.trainmanager.event.TrainTaskEvent;
+import hu.user.trainmanager.event.TrainTaskMethodName;
+import hu.user.trainmanager.event.TrainTaskMethodState;
+import hu.user.trainmanager.listenner.TrainQueueListener;
+import hu.user.trainmanager.viewmodel.ProgressPresenter;
+import lombok.Getter;
+
+@Getter
+@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
+public class TrainTaskController extends SelectorComposer<Component> implements ProgressPresenter {
+
+       private static final String ON_CLICK = "onClick";
+
+       @WireVariable private TrainProgressStateHolder trainProgressStateHolder;
+
+       @WireVariable private TrainQueueListener trainQueueListener;
+
+       @Wire private Listbox progressListbox;
+
+       @Wire private Button startButton;
+//     @Wire private Button stopButton;
+
+       @Wire private Combobox cpuCoreComboBox;
+
+       @Wire private Button trainTasksRefreshButton;
+
+       @Wire private Textbox allTrainTaskNuber;
+       @Wire private Textbox finishedTrainTaskNumber;
+       @Wire private Textbox tobeDoneTrainTaskNumber;
+       @Wire private Textbox currentLyInProgressTrainTaskId;
+
+       private TrainProgressScreenProperties trainProgressScreenProperties;
+
+       private Desktop desktop;
+
+       private ListModelList<TrainProgressState> statesModel = new ListModelList<>();
+       private ListModelList<Integer> cpuCoreModel = new ListModelList<>();
+
+       @Override
+       public void doAfterCompose(Component comp) throws Exception {
+               super.doAfterCompose(comp);
+               trainQueueListener.addPresenter(this);
+               desktop = comp.getDesktop();
+               desktop.enableServerPush(true);
+               cpuCoreComboBox.setModel(new ListModelList<>(
+                               IntStream.rangeClosed(1, trainProgressStateHolder.getAvailableCpuCoreNumber()).boxed()
+                                               .collect(Collectors.toList())));
+               cpuCoreComboBox.addEventListener("onChange", new CpuCoreComboboxChangeListener());
+               progressListbox.setModel(statesModel);
+               statesModel.addAll(new ListModelList<>(trainProgressStateHolder.getTrainProgressStates()));
+               trainProgressScreenProperties = trainProgressStateHolder.getTrainProgressScreenProperties();
+               startButton.setDisabled(trainProgressScreenProperties.isStartButtonDisabled());
+//             stopButton.setDisabled(trainProgressScreenProperties.isStopButtonDisabled());
+               allTrainTaskNuber.setValue(String.valueOf(trainProgressScreenProperties.getAllTrainTaskNumber()));
+               trainProgressScreenProperties.getFinishedTrainTaskNumber()
+                               .ifPresent(value -> finishedTrainTaskNumber.setValue(String.valueOf(value)));
+               trainProgressScreenProperties.getToBeDoneTrainTaskNumber()
+                               .ifPresent(value -> tobeDoneTrainTaskNumber.setValue(String.valueOf(value)));
+               trainProgressScreenProperties.getCurrentProgressedTrainTaskId()
+                               .ifPresent(value -> currentLyInProgressTrainTaskId.setValue(String.valueOf(value)));
+       }
+
+       @Listen("onClick = #startButton")
+       public void startTrainProgress() {
+               trainProgressScreenProperties = trainProgressStateHolder.startTrainProcess();
+               if (trainProgressScreenProperties.getToBeDoneTrainTaskNumber().isPresent()) {
+                       tobeDoneTrainTaskNumber.setValue(
+                                       String.valueOf(trainProgressScreenProperties.getToBeDoneTrainTaskNumber().get()));
+               } else {
+                       tobeDoneTrainTaskNumber.setValue(null);
+               }
+               currentLyInProgressTrainTaskId.setValue(
+                               String.valueOf(trainProgressScreenProperties.getCurrentProgressedTrainTaskId().get()));
+               startButton.setDisabled(true);
+               trainTasksRefreshButton.setDisabled(true);
+               statesModel.clear();
+               statesModel.addAll(new ListModelList<>(trainProgressStateHolder.getTrainProgressStates()));
+       }
+
+
+       @Listen("onSelect = #cpuCoreComboBox")
+       public void selectCpuCore() {
+               int coreNumber = cpuCoreComboBox.getSelectedItem().getValue();
+               trainProgressStateHolder.setUsedCpuCoreNumber(coreNumber);
+       }
+       @DefaultGlobalCommand
+       public void automaticRefreshTrainTasks(Event event) {
+               System.out.println("Event: "+event.getName());
+               if (event instanceof GlobalCommand) {
+                       if ("automaticRefreshTrainTasks".equals(((GlobalCommandEvent) event).getCommand())) {
+                               System.out.println("automatic refresh");
+                               refeshTrainTasks();
+
+                       }
+               }
+       }
+       @Listen("onClick = #trainTasksRefreshButton ")
+       public void refeshTrainTasks() {
+               trainProgressStateHolder.refreshTrainTasks();
+               statesModel.clear();
+               statesModel.addAll(new ListModelList<>(trainProgressStateHolder.getTrainProgressStates()));
+               trainProgressScreenProperties = trainProgressStateHolder.getTrainProgressScreenProperties();
+               startButton.setDisabled(trainProgressScreenProperties.isStartButtonDisabled());
+               allTrainTaskNuber.setValue(String.valueOf(trainProgressScreenProperties.getAllTrainTaskNumber()));
+               trainProgressScreenProperties.getFinishedTrainTaskNumber()
+                               .ifPresent(value -> finishedTrainTaskNumber.setValue(String.valueOf(value)));
+               trainProgressScreenProperties.getToBeDoneTrainTaskNumber()
+                               .ifPresent(value -> tobeDoneTrainTaskNumber.setValue(String.valueOf(value)));
+               trainProgressScreenProperties.getCurrentProgressedTrainTaskId()
+                               .ifPresent(value -> currentLyInProgressTrainTaskId.setValue(String.valueOf(value)));
+
+       }
+
+       @Listen("onShow = #progressListbox")
+       public void onShow(ForwardEvent forwardEvent) {
+               Button button = (Button) forwardEvent.getOrigin().getTarget();
+               Listitem listitem = (Listitem) button.getParent().getParent();
+               List<Listcell> listcells = listitem.getChildren();
+               String trainTaskid = listcells.get(0).getLabel();
+               System.out.println("id: " + trainTaskid);
+       }
+
+       @Listen("onAddButtonClick = #progressListbox")
+       public void onAddButtonClick(ForwardEvent forwardEvent) {
+               Button addTrainTaskButton = (Button) forwardEvent.getOrigin().getTarget();
+               addTrainTaskButton.setDisabled(true);
+               Listitem listitem = (Listitem) addTrainTaskButton.getParent().getParent();
+               List<Listcell> listcells = listitem.getChildren();
+               String trainTaskid = listcells.get(0).getLabel();
+               trainProgressScreenProperties = trainProgressStateHolder.addTrainTaskToWorkCollection(trainTaskid);
+               tobeDoneTrainTaskNumber.setValue(
+                               String.valueOf(trainProgressScreenProperties.getToBeDoneTrainTaskNumber().get()));
+               if (trainProgressStateHolder.getUsedCpuCoreNumber() > 0) {
+                       startButton.setDisabled(false);
+               }
+               trainTasksRefreshButton.setDisabled(true);
+       }
+
+       @Override
+       public void changeUi(TrainTaskEvent trainTaskEvent) {
+               Executions.schedule(desktop, new TriggeredListener(), new Event("triggered", progressListbox, trainTaskEvent));
+       }
+
+       class TriggeredListener implements EventListener {
+               @Override
+               public void onEvent(Event event) throws Exception {
+                       TrainTaskEvent trainTaskEvent = (TrainTaskEvent) event.getData();
+                       trainProgressScreenProperties = trainProgressStateHolder.changeProgressStatus(trainTaskEvent);
+                       List<TrainProgressState> progressStates = trainProgressStateHolder.getTrainProgressStates();
+                       statesModel.clear();
+                       statesModel.addAll(new ListModelList<>(progressStates));
+                       if (trainTaskEvent.getMethodName()
+                                       .equals(TrainTaskMethodName.METRIC_PERSISTING) && trainTaskEvent.getMethodState()
+                                       .equals(TrainTaskMethodState.FINISHED)) {
+                               if (trainProgressStateHolder.getTrainWorkCollection().isEmpty()) {
+                                       trainTasksRefreshButton.setDisabled(false);
+                                       trainProgressStateHolder.refreshAddButtonStates();
+                                       statesModel.clear();
+                                       statesModel.addAll(new ListModelList<>(progressStates));
+                                       BindUtils.postGlobalCommand(null,null,"refreshMetrics",null);
+                               }
+
+                               finishedTrainTaskNumber.setValue(
+                                               String.valueOf(trainProgressScreenProperties.getFinishedTrainTaskNumber().get()));
+                               trainProgressScreenProperties = trainProgressStateHolder.startNextTrainTask();
+                               if (trainProgressScreenProperties.getCurrentProgressedTrainTaskId().isPresent()) {
+                                       currentLyInProgressTrainTaskId.setValue(
+                                                       String.valueOf(trainProgressScreenProperties.getCurrentProgressedTrainTaskId().get()));
+                               } else {
+                                       currentLyInProgressTrainTaskId.setValue(null);
+                               }
+                               if (trainProgressScreenProperties.getToBeDoneTrainTaskNumber().isPresent()) {
+                                       tobeDoneTrainTaskNumber.setValue(
+                                                       String.valueOf(trainProgressScreenProperties.getToBeDoneTrainTaskNumber().get()));
+                               } else {
+                                       tobeDoneTrainTaskNumber.setValue(null);
+                               }
+                       }
+               }
+       }
+
+       class CpuCoreComboboxChangeListener implements EventListener {
+               @Override
+               public void onEvent(Event event) throws Exception {
+                       if (!trainProgressStateHolder.getTrainWorkCollection().isEmpty()) {
+                               startButton.setDisabled(false);
+                       } else {
+                               startButton.setDisabled(true);
+                       }
+               }
+       }
+
+}
+
+
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/CommonResponse.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/CommonResponse.java
new file mode 100644 (file)
index 0000000..66dfd28
--- /dev/null
@@ -0,0 +1,19 @@
+package hu.user.trainmanager.dto;
+
+public class CommonResponse {
+
+       private int id;
+
+       public int getId() {
+               return id;
+       }
+
+       public void setId(int id) {
+               this.id = id;
+       }
+
+       @Override
+       public String toString() {
+               return "CommonResponse{" + "id=" + id + '}';
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/CommonTrainParameters.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/CommonTrainParameters.java
new file mode 100644 (file)
index 0000000..2bbdbcb
--- /dev/null
@@ -0,0 +1,17 @@
+package hu.user.trainmanager.dto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CommonTrainParameters {
+
+       private Map<String, String> parameters = new HashMap<>();
+
+       public Map<String, String> getParameters() {
+               return parameters;
+       }
+
+       public void setParameters(Map<String, String> parameters) {
+               this.parameters = parameters;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/ConfusionMatrixDto.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/ConfusionMatrixDto.java
new file mode 100644 (file)
index 0000000..834303e
--- /dev/null
@@ -0,0 +1,21 @@
+package hu.user.trainmanager.dto;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class ConfusionMatrixDto {
+
+       private int TP;
+       private int TN;
+       private int FP;
+       private int FN;
+
+
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/DirectoryProperty.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/DirectoryProperty.java
new file mode 100644 (file)
index 0000000..6e31f2b
--- /dev/null
@@ -0,0 +1,17 @@
+package hu.user.trainmanager.dto;
+
+import java.io.File;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class DirectoryProperty {
+
+       private String displayName;
+       private File directory;
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/DownStreamErrorDto.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/DownStreamErrorDto.java
new file mode 100644 (file)
index 0000000..6cff6e0
--- /dev/null
@@ -0,0 +1,21 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+
+@Getter
+public class DownStreamErrorDto {
+
+       private int errorCode;
+       private String errorName;
+       private String errorMessage;
+
+       public DownStreamErrorDto(@JsonProperty(value = "error_code", required = true) int errorCode,
+                       @JsonProperty(value = "error_name", required = true) String errorName,
+                       @JsonProperty(value = "error_message", required = true) String errorMessage) {
+               this.errorCode = errorCode;
+               this.errorName = errorName;
+               this.errorMessage = errorMessage;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/EncodingAndFeatureEngineeringParameters.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/EncodingAndFeatureEngineeringParameters.java
new file mode 100644 (file)
index 0000000..62e499f
--- /dev/null
@@ -0,0 +1,87 @@
+package hu.user.trainmanager.dto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class EncodingAndFeatureEngineeringParameters {
+
+       private int id;
+       private String schemaName;
+       private String tableName;
+       private String timeBaseFieldName;
+       private Map<String, String> encodingParameters = new HashMap<>();
+       private FeatureEnginneringParameters featureEnginneringParameters;
+
+       public EncodingAndFeatureEngineeringParameters() {
+       }
+
+       public EncodingAndFeatureEngineeringParameters(
+                       @JsonProperty(value = "schema_name", required = true) String schemaName,
+                       @JsonProperty(value = "table_name", required = true) String tableName,
+                       @JsonProperty(value = "time_base_field_name", required = true) String timeBaseFieldName,
+                       @JsonProperty(value = "encoding_parameters", required = true) Map<String, String> encodingParameters,
+                       @JsonProperty(value = "feature_engineering_parameters", required = false)
+                       FeatureEnginneringParameters featureEnginneringParameters) {
+               this.schemaName = schemaName;
+               this.tableName = tableName;
+               this.timeBaseFieldName = timeBaseFieldName;
+               this.encodingParameters = encodingParameters;
+               this.featureEnginneringParameters = featureEnginneringParameters;
+       }
+
+       public int getId() {
+               return id;
+       }
+
+       public void setId(int id) {
+               this.id = id;
+       }
+
+       public String getSchemaName() {
+               return schemaName;
+       }
+
+       @JsonSetter("schema_name")
+       public void setSchemaName(String schemaName) {
+               this.schemaName = schemaName;
+       }
+
+       public String getTableName() {
+               return tableName;
+       }
+
+       @JsonSetter("table_name")
+       public void setTableName(String tableName) {
+               this.tableName = tableName;
+       }
+
+       public String getTimeBaseFieldName() {
+               return timeBaseFieldName;
+       }
+
+       @JsonSetter("time_base_field_name")
+       public void setTimeBaseFieldName(String timeBaseFieldName) {
+               this.timeBaseFieldName = timeBaseFieldName;
+       }
+
+       public Map<String, String> getEncodingParameters() {
+               return encodingParameters;
+       }
+
+       @JsonSetter("encoding_parameters")
+       public void setEncodingParameters(Map<String, String> encodingParameters) {
+               this.encodingParameters = encodingParameters;
+       }
+
+       public FeatureEnginneringParameters getFeatureEnginneringParameters() {
+               return featureEnginneringParameters;
+       }
+
+       @JsonSetter("feature_engineering_parameters")
+       public void setFeatureEnginneringParameters(FeatureEnginneringParameters featureEnginneringParameters) {
+               this.featureEnginneringParameters = featureEnginneringParameters;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/EstimatorAndTrainTaskIdentifiers.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/EstimatorAndTrainTaskIdentifiers.java
new file mode 100644 (file)
index 0000000..cef0e93
--- /dev/null
@@ -0,0 +1,32 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class EstimatorAndTrainTaskIdentifiers {
+
+       private int id;
+       private int trainTaskId;
+
+       public EstimatorAndTrainTaskIdentifiers(int id, @JsonProperty(value = "train_task_id") int trainTaskId) {
+               this.id = id;
+               this.trainTaskId = trainTaskId;
+       }
+
+       public int getId() {
+               return id;
+       }
+
+       public void setId(int id) {
+               this.id = id;
+       }
+
+       public int getTrainTaskId() {
+               return trainTaskId;
+       }
+
+       @JsonSetter("train_task_id")
+       public void setTrainTaskId(int trainTaskId) {
+               this.trainTaskId = trainTaskId;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/FeatureEnginneringParameters.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/FeatureEnginneringParameters.java
new file mode 100644 (file)
index 0000000..71a4b97
--- /dev/null
@@ -0,0 +1,33 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FeatureEnginneringParameters {
+
+       private String chosenFeatureField;
+       private int[] intervals;
+
+       public FeatureEnginneringParameters(@JsonProperty(value = "chosen_feature_field",required = true) String chosenFeatureField,
+                       int[] intervals) {
+               this.chosenFeatureField = chosenFeatureField;
+               this.intervals = intervals;
+       }
+
+       public String getChosenFeatureField() {
+               return chosenFeatureField;
+       }
+
+       @JsonSetter("chosen_feature_field")
+       public void setChosenFeatureField(String chosenFeatureField) {
+               this.chosenFeatureField = chosenFeatureField;
+       }
+
+       public int[] getIntervals() {
+               return intervals;
+       }
+
+       public void setIntervals(int[] intervals) {
+               this.intervals = intervals;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/FieldProperty.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/FieldProperty.java
new file mode 100644 (file)
index 0000000..4af891e
--- /dev/null
@@ -0,0 +1,35 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FieldProperty {
+
+       private String name;
+       private String[] otherApplicableEncodingTypes;
+       private String type;
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public String[] getOtherApplicableEncodingTypes() {
+               return otherApplicableEncodingTypes;
+       }
+
+       @JsonSetter("other_applicable_encoding_types")
+       public void setOtherApplicableEncodingTypes(String[] otherApplicableEncodingTypes) {
+               this.otherApplicableEncodingTypes = otherApplicableEncodingTypes;
+       }
+
+       public String getType() {
+               return type;
+       }
+
+       public void setType(String type) {
+               this.type = type;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/FileProperty.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/FileProperty.java
new file mode 100644 (file)
index 0000000..676844c
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.trainmanager.dto;
+
+import java.io.File;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class FileProperty {
+
+       private File file;
+       private String fileName;
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/FitParameters.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/FitParameters.java
new file mode 100644 (file)
index 0000000..c2879bd
--- /dev/null
@@ -0,0 +1,34 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FitParameters {
+
+       private int trainTaskId;
+       private int usedCpuCore;
+
+       public FitParameters(@JsonProperty(value = "train_task_id", required = true) int trainTaskId,
+                       @JsonProperty(value = "used_cpu_core", required = true) int usedCpuCore) {
+               this.trainTaskId = trainTaskId;
+               this.usedCpuCore = usedCpuCore;
+       }
+
+       public int getTrainTaskId() {
+               return trainTaskId;
+       }
+
+       @JsonSetter("train_task_id")
+       public void setTrainTaskId(int trainTaskId) {
+               this.trainTaskId = trainTaskId;
+       }
+
+       public int getUsedCpuCore() {
+               return usedCpuCore;
+       }
+
+       @JsonSetter("used_cpu_core")
+       public void setUsedCpuCore(int usedCpuCore) {
+               this.usedCpuCore = usedCpuCore;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/FraudCandidate.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/FraudCandidate.java
new file mode 100644 (file)
index 0000000..d95468f
--- /dev/null
@@ -0,0 +1,36 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class FraudCandidate {
+
+       private int fraudNumber;
+       private String name;
+       private int NoFraudNumber;
+
+       public int getFraudNumber() {
+               return fraudNumber;
+       }
+
+       @JsonSetter("fraud_number")
+       public void setFraudNumber(int fraudNumber) {
+               this.fraudNumber = fraudNumber;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public int getNoFraudNumber() {
+               return NoFraudNumber;
+       }
+
+       @JsonSetter("no_fraud_number")
+       public void setNoFraudNumber(int noFraudNumber) {
+               NoFraudNumber = noFraudNumber;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/LivenessDto.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/LivenessDto.java
new file mode 100644 (file)
index 0000000..7835068
--- /dev/null
@@ -0,0 +1,20 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class LivenessDto {
+
+       private String response;
+
+       public LivenessDto(@JsonProperty(value = "response",required = true) String response) {
+               this.response = response;
+       }
+
+       public String getResponse() {
+               return response;
+       }
+
+       public void setResponse(String response) {
+               this.response = response;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/TableProperties.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/TableProperties.java
new file mode 100644 (file)
index 0000000..87f0a3b
--- /dev/null
@@ -0,0 +1,54 @@
+package hu.user.trainmanager.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class TableProperties {
+
+       private List<FieldProperty> fields = new ArrayList<>();
+       private List<FraudCandidate> fraudCandidates = new ArrayList<>();
+       private String primaryKey;
+       private int recordNumber;
+
+       public List<FieldProperty> getFields() {
+               return fields;
+       }
+
+       public void setFields(List<FieldProperty> fields) {
+               this.fields = fields;
+       }
+
+       public List<FraudCandidate> getFraudCandidates() {
+               return fraudCandidates;
+       }
+
+       @JsonSetter("fraud_candidates")
+       public void setFraudCandidates(List<FraudCandidate> fraudCandidates) {
+               this.fraudCandidates = fraudCandidates;
+       }
+
+       public String getPrimaryKey() {
+               return primaryKey;
+       }
+
+       @JsonSetter("primary_key")
+       public void setPrimaryKey(String primaryKey) {
+               this.primaryKey = primaryKey;
+       }
+
+       public int getRecordNumber() {
+               return recordNumber;
+       }
+
+       @JsonSetter("record_number")
+       public void setRecordNumber(int recordNumber) {
+               this.recordNumber = recordNumber;
+       }
+
+       @Override
+       public String toString() {
+               return "TableProperties{" + "fields=" + fields + ", fraudCandidates=" + fraudCandidates + ", primaryKey='" + primaryKey + '\'' + ", recordNumber=" + recordNumber + '}';
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/TrainTaskParameters.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/TrainTaskParameters.java
new file mode 100644 (file)
index 0000000..e2d3920
--- /dev/null
@@ -0,0 +1,128 @@
+package hu.user.trainmanager.dto;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+import hu.user.trainmanager.status.TrainTaskState;
+
+public class TrainTaskParameters {
+       private String trainTaskName;
+       private String uniqueFieldName;
+       private int plannedEncodingAndFeatureEngineeringId;
+       private int featureSelectorCode;
+       private int samplerCode;
+       private int scalerCode;
+       private int model_code;
+       private double testSize;
+       private TrainTaskState trainTaskState;
+
+       public TrainTaskParameters() {
+       }
+
+       // @formatter:off
+       public TrainTaskParameters(
+                       @JsonProperty(value = "train_task_name",required = true) String trainTaskName,
+                       @JsonProperty(value = "unique_field_name",required = true) String uniqueFieldName,
+                       @JsonProperty(value = "planned_encoding_and_feature_engineering_id", required = true) int plannedEncodingAndFeatureEngineeringId,
+                       @JsonProperty(value = "feature_selector_code", required = true) int featureSelectorCode,
+                       @JsonProperty(value = "sampler_code", required = true) int samplerCode,
+                       @JsonProperty(value = "scaler_code", required = true) int scalerCode,
+                       @JsonProperty(value = "model_code", required = true) int model_code,
+                       @JsonProperty(value = "test_size", required = true) double testSize,
+                       @JsonProperty(value = "process_status",required = true) TrainTaskState trainTaskState) {
+               this.trainTaskName = trainTaskName;
+               this.uniqueFieldName=uniqueFieldName;
+               this.plannedEncodingAndFeatureEngineeringId = plannedEncodingAndFeatureEngineeringId;
+               this.featureSelectorCode = featureSelectorCode;
+               this.samplerCode = samplerCode;
+               this.scalerCode = scalerCode;
+               this.model_code = model_code;
+               this.testSize = testSize;
+               this.trainTaskState = trainTaskState;
+       }
+       // @formatter:on
+       @JsonGetter("train_task_name")
+       public String getTrainTaskName() {
+               return trainTaskName;
+       }
+
+       @JsonSetter("train_task_name")
+       public void setTrainTaskName(String trainTaskName) {
+               this.trainTaskName = trainTaskName;
+       }
+
+       @JsonSetter("unique_field_name")
+       public void setUniqueFieldName(String uniqueFieldName) {
+               this.uniqueFieldName = uniqueFieldName;
+       }
+
+       @JsonGetter("unique_field_name")
+       public String getUniqueFieldName() {
+               return uniqueFieldName;
+       }
+
+       public int getPlannedEncodingAndFeatureEngineeringId() {
+               return plannedEncodingAndFeatureEngineeringId;
+       }
+
+       @JsonSetter("planned_encoding_and_feature_engineering_id")
+       public void setPlannedEncodingAndFeatureEngineeringId(int plannedEncodingAndFeatureEngineeringId) {
+               this.plannedEncodingAndFeatureEngineeringId = plannedEncodingAndFeatureEngineeringId;
+       }
+
+       public int getFeatureSelectorCode() {
+               return featureSelectorCode;
+       }
+
+       @JsonSetter("feature_selector_code")
+       public void setFeatureSelectorCode(int featureSelectorCode) {
+               this.featureSelectorCode = featureSelectorCode;
+       }
+
+       public int getSamplerCode() {
+               return samplerCode;
+       }
+
+       @JsonSetter("sampler_code")
+       public void setSamplerCode(int samplerCode) {
+               this.samplerCode = samplerCode;
+       }
+
+       public int getScalerCode() {
+               return scalerCode;
+       }
+
+       @JsonSetter("scaler_code")
+       public void setScalerCode(int scalerCode) {
+               this.scalerCode = scalerCode;
+       }
+
+       public int getModel_code() {
+               return model_code;
+       }
+
+       @JsonSetter("model_code")
+       public void setModel_code(int model_code) {
+               this.model_code = model_code;
+       }
+
+       public double getTestSize() {
+               return testSize;
+       }
+
+       @JsonSetter("test_size")
+       public void setTestSize(double testSize) {
+               this.testSize = testSize;
+       }
+
+       public TrainTaskState getTrainTaskState() {
+               return trainTaskState;
+       }
+
+       @JsonSetter("process_status")
+       public void setTrainTaskState(TrainTaskState trainTaskState) {
+               this.trainTaskState = trainTaskState;
+       }
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/CsvContentDto.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/CsvContentDto.java
new file mode 100644 (file)
index 0000000..1ea6b38
--- /dev/null
@@ -0,0 +1,24 @@
+package hu.user.trainmanager.dto.csv;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class CsvContentDto {
+
+       private List<String> headerElements = new ArrayList<>();
+       private List<List<String>> lines = new ArrayList<>();
+
+       public CsvContentDto(@JsonProperty(value = "header_elements", required = true) List<String> headerElements,
+                       @JsonProperty(value = "lines", required = true) List<List<String>> lines) {
+               this.headerElements = headerElements;
+               this.lines = lines;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/CsvDataHolder.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/CsvDataHolder.java
new file mode 100644 (file)
index 0000000..6a1707c
--- /dev/null
@@ -0,0 +1,17 @@
+package hu.user.trainmanager.dto.csv;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class CsvDataHolder {
+
+       private List<String> headerElements = new ArrayList<>();
+       private List<List<String>> contentLines = new ArrayList<>();
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/ReducingAndEvaulationParameterRequestDto.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/ReducingAndEvaulationParameterRequestDto.java
new file mode 100644 (file)
index 0000000..a13b741
--- /dev/null
@@ -0,0 +1,35 @@
+package hu.user.trainmanager.dto.csv;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ReducingAndEvaulationParameterRequestDto {
+
+       public String schemaName;
+       private String tableName;
+       private String fraudTypeFieldName;
+       private int positiveFraudCaseNumber;
+       private int negatíveFraudCaseNumber;
+       private boolean dateSuffixToAppend;
+
+       // @formatter:off
+       public ReducingAndEvaulationParameterRequestDto(
+                       @JsonProperty(value = "schema_name", required = true) String schemaName,
+                       @JsonProperty(value = "table_name", required = true) String tableName,
+                       @JsonProperty(value = "fraud_type_field_name", required = true) String fraudTypeFieldName,
+                       @JsonProperty(value = "positive_fraud_case_number", required = true) int positiveFraudCaseNumber,
+                       @JsonProperty(value = "negative_fraud_case_number", required = true) int negatíveFraudCaseNumber,
+                       @JsonProperty(value = "date_suffix_to_append",required = true)  boolean dateSuffixToAppend) {
+       // @formatter:on
+               this.schemaName = schemaName;
+               this.tableName = tableName;
+               this.fraudTypeFieldName = fraudTypeFieldName;
+               this.positiveFraudCaseNumber = positiveFraudCaseNumber;
+               this.negatíveFraudCaseNumber = negatíveFraudCaseNumber;
+               this.dateSuffixToAppend = dateSuffixToAppend;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/ReducingAndEvaulationParameterResponseDto.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/csv/ReducingAndEvaulationParameterResponseDto.java
new file mode 100644 (file)
index 0000000..f6e7605
--- /dev/null
@@ -0,0 +1,26 @@
+package hu.user.trainmanager.dto.csv;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ReducingAndEvaulationParameterResponseDto {
+
+       private String plannedCsvFileName;
+       private Map<Integer, Integer> plannedCsvContent = new HashMap<>();
+
+       // @formatter:off
+       public ReducingAndEvaulationParameterResponseDto(
+                       @JsonProperty(value = "planned_csv_file_name",required = true) String plannedCsvFileName,
+                       @JsonProperty(value = "planned_csv_content",required = true) Map<Integer, Integer> plannedCsvContent) {
+               this.plannedCsvFileName = plannedCsvFileName;
+               this.plannedCsvContent = plannedCsvContent;
+       }
+       // @formatter:on
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/display/EstimatorMetricsDto.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/display/EstimatorMetricsDto.java
new file mode 100644 (file)
index 0000000..925b7d2
--- /dev/null
@@ -0,0 +1,35 @@
+package hu.user.trainmanager.dto.display;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class EstimatorMetricsDto {
+       private int id;
+       private int tp;
+       private int tn;
+       private int fp;
+       private int fn;
+       private double sensitivity;
+       private double specifity;
+       private double accuracy;
+       private double balancedAccuracy;
+       private double prec;
+       private double recall;
+       private double ppv;
+       private double npv;
+       private double fnr;
+       private double fpr;
+       private double fdr;
+       private double _for;
+       private double f1;
+       private double f05;
+       private double f2;
+       private double mcc;
+       private double rocauc;
+       private double youden;
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/display/FieldNameAndEncodingType.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/display/FieldNameAndEncodingType.java
new file mode 100644 (file)
index 0000000..ccde14f
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.trainmanager.dto.display;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class FieldNameAndEncodingType {
+
+       private String fieldName;
+       private String encodingType;
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/progress/TrainProgressScreenProperties.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/progress/TrainProgressScreenProperties.java
new file mode 100644 (file)
index 0000000..56f65dd
--- /dev/null
@@ -0,0 +1,21 @@
+package hu.user.trainmanager.dto.progress;
+
+import java.util.Optional;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class TrainProgressScreenProperties {
+
+       private int allTrainTaskNumber;
+       private Optional<Integer> finishedTrainTaskNumber;
+       private Optional<Integer> toBeDoneTrainTaskNumber;
+       private Optional<Integer> currentProgressedTrainTaskId;
+       private boolean startButtonDisabled;
+       private boolean stopButtonDisabled;
+       private boolean trainTaskRefreshButtonDisabled;
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/dto/progress/TrainProgressState.java b/TrainManager/src/main/java/hu/user/trainmanager/dto/progress/TrainProgressState.java
new file mode 100644 (file)
index 0000000..7047d60
--- /dev/null
@@ -0,0 +1,25 @@
+package hu.user.trainmanager.dto.progress;
+
+import hu.user.trainmanager.event.TrainTaskMethodState;
+import hu.user.trainmanager.status.TrainTaskState;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class TrainProgressState {
+
+       private int id;
+       private String trainTaskName;
+       private TrainTaskMethodState encodingState;
+       private TrainTaskMethodState featureEngineeringState;
+       private TrainTaskMethodState samplingProcessState;
+       private TrainTaskMethodState fitProcessState;
+       private TrainTaskMethodState estimatorPersistingState;
+       private TrainTaskMethodState metricsPersistingState;
+       private TrainTaskState trainTaskState;
+       private boolean addButtonDisabled;
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/event/MessageArrivedFromTrainModule.java b/TrainManager/src/main/java/hu/user/trainmanager/event/MessageArrivedFromTrainModule.java
new file mode 100644 (file)
index 0000000..f067c63
--- /dev/null
@@ -0,0 +1,9 @@
+package hu.user.trainmanager.event;
+
+import org.springframework.context.ApplicationEvent;
+
+public class MessageArrivedFromTrainModule extends ApplicationEvent {
+       public MessageArrivedFromTrainModule(Object source) {
+               super(source);
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/event/TrainLiveCheckEvent.java b/TrainManager/src/main/java/hu/user/trainmanager/event/TrainLiveCheckEvent.java
new file mode 100644 (file)
index 0000000..0ed35d7
--- /dev/null
@@ -0,0 +1,17 @@
+package hu.user.trainmanager.event;
+
+import org.springframework.context.ApplicationEvent;
+
+import hu.user.trainmanager.status.TrainStatus;
+import lombok.Getter;
+
+@Getter
+public class TrainLiveCheckEvent extends ApplicationEvent {
+
+       private TrainStatus trainStatus;
+
+       public TrainLiveCheckEvent(Object source, TrainStatus trainStatus) {
+               super(source);
+               this.trainStatus = trainStatus;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskEvent.java b/TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskEvent.java
new file mode 100644 (file)
index 0000000..9b379ef
--- /dev/null
@@ -0,0 +1,49 @@
+package hu.user.trainmanager.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+
+public class TrainTaskEvent {
+
+       private int trainTaskId;
+       private TrainTaskMethodName methodName;
+       private TrainTaskMethodState methodState;
+
+       public TrainTaskEvent(@JsonProperty(value = "train_task_id", required = true) int trainTaskId,
+                       @JsonProperty(value = "method", required = true) TrainTaskMethodName methodName,
+                       @JsonProperty(value = "status", required = true) TrainTaskMethodState methodState) {
+               this.trainTaskId = trainTaskId;
+               this.methodName = methodName;
+               this.methodState = methodState;
+       }
+
+       public int getTrainTaskId() {
+               return trainTaskId;
+       }
+
+       @JsonSetter("train_task_id")
+       public void setTrainTaskId(int trainTaskId) {
+               this.trainTaskId = trainTaskId;
+       }
+
+       public TrainTaskMethodName getMethodName() {
+               return methodName;
+       }
+
+       public void setMethodName(TrainTaskMethodName methodName) {
+               this.methodName = methodName;
+       }
+
+       public TrainTaskMethodState getMethodState() {
+               return methodState;
+       }
+
+       public void setMethodState(TrainTaskMethodState methodState) {
+               this.methodState = methodState;
+       }
+
+       @Override
+       public String toString() {
+               return "TrainTaskEvent{" + "trainTaskId=" + trainTaskId + ", methodName=" + methodName + ", methodState=" + methodState + '}';
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskMethodName.java b/TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskMethodName.java
new file mode 100644 (file)
index 0000000..1b48cf6
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.trainmanager.event;
+
+public enum TrainTaskMethodName {
+
+       // @formatter:off
+       ENCODING,
+       FEATURE_ENGINEERING,
+       SAMPLING,
+       FITTING,
+       ESTIMATOR_PERSISTING,
+       METRIC_PERSISTING;
+       // @formatter:on
+
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskMethodState.java b/TrainManager/src/main/java/hu/user/trainmanager/event/TrainTaskMethodState.java
new file mode 100644 (file)
index 0000000..f3a8660
--- /dev/null
@@ -0,0 +1,5 @@
+package hu.user.trainmanager.event;
+
+public enum TrainTaskMethodState {
+       READY, PREVIOUSLY_DONE, STARTED, FINISHED
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/exception/ParametersExistYetException.java b/TrainManager/src/main/java/hu/user/trainmanager/exception/ParametersExistYetException.java
new file mode 100644 (file)
index 0000000..e8db466
--- /dev/null
@@ -0,0 +1,14 @@
+package hu.user.trainmanager.exception;
+
+public class ParametersExistYetException extends RuntimeException{
+
+       private String errorMessage;
+
+       public ParametersExistYetException(String errorMessage) {
+               this.errorMessage = errorMessage;
+       }
+
+       public String getErrorMessage() {
+               return errorMessage;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/listenner/TrainQueueListener.java b/TrainManager/src/main/java/hu/user/trainmanager/listenner/TrainQueueListener.java
new file mode 100644 (file)
index 0000000..9823bf3
--- /dev/null
@@ -0,0 +1,45 @@
+package hu.user.trainmanager.listenner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.stereotype.Service;
+
+import hu.user.trainmanager.component.TrainProgressStateHolder;
+import hu.user.trainmanager.event.TrainTaskEvent;
+import hu.user.trainmanager.viewmodel.ProgressPresenter;
+
+@Service
+@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+public class TrainQueueListener {
+       @Autowired private TrainProgressStateHolder trainProgressStateHolder;
+
+       private List<ProgressPresenter> presenters = new ArrayList<>();
+
+       public void addPresenter(ProgressPresenter presenter) {
+               presenters.clear();
+               presenters.add(presenter);
+       }
+
+       @RabbitListener(queues = "train")
+       public void receiveEvent(TrainTaskEvent event) {
+               System.out.println("TrainQueueListener hashCode in receiveEvent: " + this.hashCode());
+               System.out.println(event);
+//             trainProgressStateHolder.changeProgressStatus(event);
+//             Events.postEvent(new Event("onTrigger"));
+               presenters.stream().forEach(e -> e.changeUi(event));
+       }
+}
+
+
+
+
+
+
+
+
+
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/service/CsvHandler.java b/TrainManager/src/main/java/hu/user/trainmanager/service/CsvHandler.java
new file mode 100644 (file)
index 0000000..64bc2db
--- /dev/null
@@ -0,0 +1,86 @@
+package hu.user.trainmanager.service;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.stereotype.Service;
+
+import com.opencsv.CSVWriter;
+
+import hu.user.trainmanager.dto.csv.CsvContentDto;
+import hu.user.trainmanager.dto.csv.CsvDataHolder;
+import lombok.AllArgsConstructor;
+
+@Service
+@AllArgsConstructor
+public class CsvHandler {
+
+       private TrainClient trainClient;
+
+       public void createEvaulationCsvFile(String fullPath, String fileName, Map<Integer, Integer> csvContent) {
+               List<String> headerElements = new ArrayList<>(List.of("id", "fraud"));
+               List<List<String>> contentLines =
+                               csvContent.entrySet().stream().map(e -> createLine(e)).collect(Collectors.toList());
+               CsvDataHolder csvDataHolder = new CsvDataHolder(headerElements, contentLines);
+               String filePath = fullPath + "/" + fileName;
+               filePath += ".csv";
+               writeDataLineByLine(filePath, csvDataHolder);
+       }
+
+       private List<String> createLine(Map.Entry<Integer, Integer> e) {
+               return List.of(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
+       }
+
+       public void createTrainTaskCsvFile(String fullPath, String fileName, boolean dateTimeSuffixEnabled) {
+               CsvContentDto csvContentDto = trainClient.getTrainTaskCsvContent();
+               CsvDataHolder csvDataHolder = new CsvDataHolder(csvContentDto.getHeaderElements(), csvContentDto.getLines());
+               String filePath = fullPath + "/" + fileName;
+               if (dateTimeSuffixEnabled) {
+                       LocalDateTime now = LocalDateTime.now();
+                       DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_hhmmss");
+                       String formattedDateTime = now.format(formatter);
+                       filePath += "_" + formattedDateTime;
+               }
+               filePath += ".csv";
+               writeDataLineByLine(filePath, csvDataHolder);
+       }
+
+       public void createMetricsCsvFile(String fullPath, String fileName, boolean dateTimeSuffixEnabled) {
+               CsvContentDto csvContentDto = trainClient.getMetricsCsvContent();
+               CsvDataHolder csvDataHolder = new CsvDataHolder(csvContentDto.getHeaderElements(), csvContentDto.getLines());
+               String filePath = fullPath + "/" + fileName;
+               if (dateTimeSuffixEnabled) {
+                       LocalDateTime now = LocalDateTime.now();
+                       DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_hhmmss");
+                       String formattedDateTime = now.format(formatter);
+                       filePath += "_" + formattedDateTime;
+               }
+               filePath += ".csv";
+               writeDataLineByLine(filePath, csvDataHolder);
+       }
+
+       public void writeDataLineByLine(String filePath, CsvDataHolder csvDataHolder) {
+               File file = new File(filePath);
+               List<String> headerElements = csvDataHolder.getHeaderElements();
+               List<List<String>> contentLines = csvDataHolder.getContentLines();
+               try (FileWriter outputfile = new FileWriter(file)) {
+                       CSVWriter writer = new CSVWriter(outputfile);
+                       String[] header = headerElements.stream().toArray(String[]::new);
+                       writer.writeNext(header);
+                       for (List<String> lineItems : contentLines) {
+                               String[] items = lineItems.stream().toArray(String[]::new);
+                               writer.writeNext(items);
+                       }
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/service/MetricsCsvDataBuilder.java b/TrainManager/src/main/java/hu/user/trainmanager/service/MetricsCsvDataBuilder.java
new file mode 100644 (file)
index 0000000..6a7f40a
--- /dev/null
@@ -0,0 +1,53 @@
+package hu.user.trainmanager.service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.stereotype.Service;
+
+import hu.user.trainmanager.dto.csv.CsvDataHolder;
+import lombok.AllArgsConstructor;
+
+@Service
+@AllArgsConstructor
+public class MetricsCsvDataBuilder {
+
+       private static final String ESTIMATOR_ID = "estimator_id";
+       private static final String TRAIN_TASK_NAME = "train_task_name";
+       private static final List<String> METRIC_NAMES = new ArrayList<>(
+                       Arrays.asList("TP", "FP", "TN", "FN", "sensitivity", "specificity", "accuracy", "balanced_accuracy", "prec",
+                                       "recall", "PPV", "NPV", "FNR", "FPR", "FDR", "FOR", "f1", "f0.5", "f2", "MCC", "ROCAUC", "Youdens_statistic"));
+
+       private TrainClient trainClient;
+
+       public CsvDataHolder getCsvContent() {
+               List<String> headerElements = new ArrayList<>();
+               List<List<String>> lines = new ArrayList<>();
+               List<Map<String, Double>> metricsFromTrainModule = trainClient.getMetrics(Collections.emptyMap());
+               List<Integer> estimatorIds =
+                               metricsFromTrainModule.get(0).entrySet().stream().filter(e -> e.getKey().equals(ESTIMATOR_ID))
+                                               .map(e -> e.getValue().intValue()).collect(Collectors.toList());
+               List<String> estimatorNames = estimatorIds.stream()
+                               .map(e -> trainClient.getTrainTaskNameByEstimatorId(Map.of(ESTIMATOR_ID, String.valueOf(e))))
+                               .collect(Collectors.toList());
+               headerElements.add(TRAIN_TASK_NAME);
+               headerElements.addAll(METRIC_NAMES);
+
+               int dataLineNumber = metricsFromTrainModule.size();
+               for (int i = 0; i < dataLineNumber; i++) {
+                       List<String> singleLine = new ArrayList<>();
+                       singleLine.add(estimatorNames.get(i));
+                       Map<String, Double> singleMetrics = metricsFromTrainModule.get(i);
+                       for (String metricName : METRIC_NAMES) {
+                               singleLine.add(String.valueOf(singleMetrics.get(metricName)));
+                       }
+                       lines.add(singleLine);
+               }
+               CsvDataHolder csvDataHolder = new CsvDataHolder(headerElements, lines);
+               return csvDataHolder;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/service/TrainClient.java b/TrainManager/src/main/java/hu/user/trainmanager/service/TrainClient.java
new file mode 100644 (file)
index 0000000..faa2af0
--- /dev/null
@@ -0,0 +1,350 @@
+package hu.user.trainmanager.service;
+
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestTemplate;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import hu.user.trainmanager.dto.ConfusionMatrixDto;
+import hu.user.trainmanager.dto.DownStreamErrorDto;
+import hu.user.trainmanager.dto.EncodingAndFeatureEngineeringParameters;
+import hu.user.trainmanager.dto.EstimatorAndTrainTaskIdentifiers;
+import hu.user.trainmanager.dto.FitParameters;
+import hu.user.trainmanager.dto.LivenessDto;
+import hu.user.trainmanager.dto.TableProperties;
+import hu.user.trainmanager.dto.TrainTaskParameters;
+import hu.user.trainmanager.dto.csv.CsvContentDto;
+import hu.user.trainmanager.dto.csv.ReducingAndEvaulationParameterRequestDto;
+import hu.user.trainmanager.dto.csv.ReducingAndEvaulationParameterResponseDto;
+import hu.user.trainmanager.status.TrainModuleStatus;
+import lombok.extern.slf4j.Slf4j;
+
+@Service
+@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Slf4j
+public class TrainClient {
+       private static final String OK = "OK";
+       private static final String ENCODING_AND_FEATURE_ENGINEERING_PATH = "/encoding_and_feature_engineering_plan";
+       private static final String TRAIN_TASK_PATH = "/train_task";
+       private static final String FIT_PATH = "/fit";
+       private static final String SCHEMA_PATH = "/schema";
+       private static final String TABLE_PATH = "/table";
+       private static final String FIELD_NAME_AND_TYPE_PATH = "/field_name_and_type";
+       private static final String FIELD_NAME_IN_ORIDNAL_POSITION_PATH = "/field_name_in_ordinal_position";
+       private static final String FIELD_TYPE_IN_ORIDNAL_POSITION_PATH = "/field_type_in_ordinal_position";
+       private static final String RECORD_PATH = "/record";
+       private static final String METRICS_PATH = "/metrics";
+       private static final String CPU_PATH = "/cpu";
+       private static final String ESTIMATOR_PATH = "/estimator";
+       private static final String CONFUSION_MATRIX_PATH = "/confusion_matrix";
+       private static final String LIVENESS_PATH = "/liveness";
+       private static final String TABLE_INFO_PATH = "/table_info";
+       private static final String TRAIN_TASK_NAME_BY_ESTIMATOR_ID_PATH = "/train_task_name_by_estimator_id";
+       private static final String TRAIN_TASK_CSV_CONTENT_PATH = "/train_task_csv_content";
+       private static final String METRICS_CSV_CONTENT_PATH = "/metrics_csv_content";
+       private static final String COLUMNS_CONTAIN_ONLY_DISTINCT_VALUES_PATH = "/columns_contain_only_distinct_values";
+       private static final String REDUCED_AND_EVAULATION_PATH = "/reduced_and_evaulation";
+       private static final String MAX_RETROSPECTIVE_DAY = "/max_retrospective_day";
+
+       @Value("${home.directory}") private String homeDirectory;
+       @Value("${evaluation.csv.directory}") private String evaluationCsvDirectory;
+       @Value("${trainUrl}") String trainUrl;
+       @Autowired private RestTemplate restTemplate;
+       private final ObjectMapper objectMapper = new ObjectMapper();
+
+       public String getHomeDirectory() {
+               return homeDirectory;
+       }
+       public String getEvaluationCsvDirectory() {
+               return evaluationCsvDirectory;
+       }
+
+       public TrainModuleStatus testLiveness() {
+               TrainModuleStatus trainModuleStatus = null;
+               String url = trainUrl + LIVENESS_PATH;
+               try {
+                       LivenessDto livenessDto = restTemplate.getForEntity(url, LivenessDto.class).getBody();
+                       if (livenessDto.getResponse().equals(OK)) {
+                               trainModuleStatus = TrainModuleStatus.OK;
+                       }
+               } catch (ResourceAccessException exception) {
+                       if (exception.getCause() instanceof ConnectException) {
+                               trainModuleStatus = TrainModuleStatus.NOT_ACCESSIBLE;
+                       }
+                       if (exception.getCause() instanceof SocketTimeoutException) {
+                               //TO-DO logging
+                               trainModuleStatus = TrainModuleStatus.RESPONSE_TIMEOUT;
+                       }
+               }
+               return trainModuleStatus;
+       }
+
+       public int getCpuCoreNumber() {
+               String url = trainUrl + CPU_PATH;
+               int coreNumber = restTemplate.getForEntity(url, Integer.class).getBody();
+               return coreNumber;
+       }
+
+       public String[] getSchemas() {
+               String url = trainUrl + SCHEMA_PATH;
+               String[] schemas = restTemplate.getForEntity(url, String[].class).getBody();
+               return schemas;
+       }
+
+       public String[] getTables(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TABLE_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               String[] tables = restTemplate.getForEntity(urlTemplate, String[].class, params).getBody();
+               return tables;
+       }
+
+       public TableProperties getTableProperties(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TABLE_INFO_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<TableProperties> tableProperties =
+                               restTemplate.getForEntity(urlTemplate, TableProperties.class, params);
+               TableProperties result = tableProperties.getBody();
+               return result;
+       }
+
+       public String[] getFieldNameInOrdinalPosition(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + FIELD_NAME_IN_ORIDNAL_POSITION_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               String[] tables = restTemplate.getForEntity(urlTemplate, String[].class, params).getBody();
+               return tables;
+       }
+
+       public String[] getFieldTypeInOrdinalPosition(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + FIELD_TYPE_IN_ORIDNAL_POSITION_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               String[] tables = restTemplate.getForEntity(urlTemplate, String[].class, params).getBody();
+               return tables;
+       }
+
+       public Map<String, String> getFieldTypeByName(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + FIELD_NAME_AND_TYPE_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<Map<String, String>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               Map<String, String> result =
+                               restTemplate.exchange(urlTemplate, HttpMethod.GET, null, typeReference, params).getBody();
+               return result;
+       }
+
+       public String getRecordsAsString(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + RECORD_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> tableProperties = restTemplate.getForEntity(urlTemplate, String.class, params);
+               String result = tableProperties.getBody();
+               return result;
+       }
+
+       public List<List<Object>> getRecords(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + RECORD_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<List<List<Object>>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               List<List<Object>> records = exchangeAsList(urlTemplate, typeReference, params);
+               return records;
+       }
+
+       public List<EncodingAndFeatureEngineeringParameters> getEncodingAndFeatureEngineeringParameters(
+                       Map<String, String> queryParamValueByName) {
+               String url = trainUrl + ENCODING_AND_FEATURE_ENGINEERING_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<List<EncodingAndFeatureEngineeringParameters>> typeReference =
+                               new ParameterizedTypeReference<>() {
+                               };
+               List<EncodingAndFeatureEngineeringParameters> encodingAndFeatureEngineeringParameters =
+                               exchangeAsList(urlTemplate, typeReference, params);
+               return encodingAndFeatureEngineeringParameters;
+       }
+
+       public Map<Integer, TrainTaskParameters> getTrainTask(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TRAIN_TASK_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<Map<Integer, TrainTaskParameters>> typeReference =
+                               new ParameterizedTypeReference<>() {
+                               };
+               Map<Integer, TrainTaskParameters> result =
+                               restTemplate.exchange(urlTemplate, HttpMethod.GET, null, typeReference, params).getBody();
+               return result;
+       }
+
+       public Map<String, String> getFeatureSelectors() {
+               return useCommmonQuery("/available_feature_selector");
+       }
+
+       public Map<String, String> getSamplers() {
+               return useCommmonQuery("/available_sampler");
+       }
+
+       public Map<String, String> getScalers() {
+               return useCommmonQuery("/available_scaler");
+       }
+
+       public Map<String, String> getModels() {
+               return useCommmonQuery("/available_model");
+       }
+
+       public Map<String, String> useCommmonQuery(String path) {
+               String url = trainUrl + path;
+               Map<String, String> parameters = restTemplate.getForEntity(url, Map.class).getBody();
+               return parameters;
+       }
+
+       public List<EstimatorAndTrainTaskIdentifiers> getEstimatorIdentifiers() {
+               String url = trainUrl + "/estimator";
+               ParameterizedTypeReference<List<EstimatorAndTrainTaskIdentifiers>> typeReference =
+                               new ParameterizedTypeReference<>() {
+                               };
+               List<EstimatorAndTrainTaskIdentifiers> estimatorAndTrainTaskIdentifiers =
+                               exchangeAsList(url, typeReference, Collections.emptyMap());
+               return estimatorAndTrainTaskIdentifiers;
+       }
+
+       public List<Map<String, Double>> getMetrics(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + METRICS_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ParameterizedTypeReference<List<Map<String, Double>>> typeReference = new ParameterizedTypeReference<>() {
+               };
+               List<Map<String, Double>> metrics = exchangeAsList(urlTemplate, typeReference, params);
+               return metrics;
+       }
+
+       public ConfusionMatrixDto getConfusionMatrixElements(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + CONFUSION_MATRIX_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<ConfusionMatrixDto> responseEntity =
+                               restTemplate.getForEntity(urlTemplate, ConfusionMatrixDto.class, params);
+               return responseEntity.getBody();
+       }
+
+       public String getTrainTaskNameByEstimatorId(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + TRAIN_TASK_NAME_BY_ESTIMATOR_ID_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               ResponseEntity<String> tableProperties = restTemplate.getForEntity(urlTemplate, String.class, params);
+               String result = tableProperties.getBody();
+               return result;
+       }
+
+       public CsvContentDto getTrainTaskCsvContent() {
+               String url = trainUrl + TRAIN_TASK_CSV_CONTENT_PATH;
+               return restTemplate.getForEntity(url, CsvContentDto.class, Collections.emptyMap()).getBody();
+       }
+
+       public CsvContentDto getMetricsCsvContent() {
+               String url = trainUrl + METRICS_CSV_CONTENT_PATH;
+               return restTemplate.getForEntity(url, CsvContentDto.class, Collections.emptyMap()).getBody();
+       }
+
+       public List<String> getColumnsContainOnlyDistinctValues(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + COLUMNS_CONTAIN_ONLY_DISTINCT_VALUES_PATH;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               return Arrays.stream(restTemplate.getForEntity(urlTemplate, String[].class, params).getBody())
+                               .collect(Collectors.toList());
+       }
+
+       public int getMaxRetroSpectiveDay(Map<String, String> queryParamValueByName) {
+               String url = trainUrl + MAX_RETROSPECTIVE_DAY;
+               UrlBuilder urlBuilder = new UrlBuilder();
+               urlBuilder.build(url, queryParamValueByName);
+               String urlTemplate = urlBuilder.getUrl();
+               Map<String, String> params = urlBuilder.getQueryParams();
+               return restTemplate.getForEntity(urlTemplate, Integer.class, params).getBody();
+       }
+
+       public int addEncodingAndFeatureEngineeringParameters(EncodingAndFeatureEngineeringParameters parameters) {
+               return addGenericParameters(parameters, ENCODING_AND_FEATURE_ENGINEERING_PATH);
+       }
+
+       public int addTrainTask(TrainTaskParameters trainTaskParameters) {
+               return addGenericParameters(trainTaskParameters, TRAIN_TASK_PATH);
+       }
+
+       public ReducingAndEvaulationParameterResponseDto createReducedAndEvaulationTables(ReducingAndEvaulationParameterRequestDto requestDto) {
+               String url = trainUrl + REDUCED_AND_EVAULATION_PATH;
+               return restTemplate.postForEntity(url, requestDto, ReducingAndEvaulationParameterResponseDto.class, Collections.emptyMap()).getBody();
+       }
+
+       public void fit(FitParameters parameters) {
+               addGenericParameters(parameters, FIT_PATH);
+       }
+
+       public <T> Integer addGenericParameters(T parameters, String path) {
+               String url = trainUrl + path;
+               try {
+                       ResponseEntity<Integer> responseFromTrainModule =
+                                       restTemplate.postForEntity(url, parameters, Integer.class);
+                       return responseFromTrainModule.getBody();
+               } catch (HttpClientErrorException httpClientErrorException) {
+                       String errorMessage = httpClientErrorException.getResponseBodyAsString();
+                       log.error(errorMessage);
+                       throw new RuntimeException(errorMessage);
+               }
+       }
+
+       private <T> List<T> exchangeAsList(String uri, ParameterizedTypeReference<List<T>> responseType,
+                       Map<String, String> uriVariables) {
+               return restTemplate.exchange(uri, HttpMethod.GET, null, responseType, uriVariables).getBody();
+       }
+
+}
+
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/service/UrlBuilder.java b/TrainManager/src/main/java/hu/user/trainmanager/service/UrlBuilder.java
new file mode 100644 (file)
index 0000000..2b47618
--- /dev/null
@@ -0,0 +1,50 @@
+package hu.user.trainmanager.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class UrlBuilder {
+
+       private String url;
+       private Map<String, String> queryParams = new HashMap<>();
+
+       public String getUrl() {
+               return url;
+       }
+
+       public Map<String, String> getQueryParams() {
+               return queryParams;
+       }
+
+       public void build(String url, Map<String, String> queryValueByParamName) {
+               List<String> paramNames = new ArrayList<>();
+               List<String> paramValues = new ArrayList<>();
+               for (var entry : queryValueByParamName.entrySet()) {
+                       paramNames.add(entry.getKey());
+                       paramValues.add(entry.getValue());
+               }
+               buildUrl(url, paramNames);
+               fillQueryParams(paramNames, paramValues);
+       }
+
+       private void buildUrl(String url, List<String> queryParamNames) {
+               String urlTemplate;
+               UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url);
+               for (String queryParamName : queryParamNames) {
+                       uriComponentsBuilder = uriComponentsBuilder.queryParam(queryParamName, "{" + queryParamName + "}");
+               }
+               this.url = uriComponentsBuilder.encode().toUriString();
+       }
+
+       private void fillQueryParams(List<String> paramNames, List<String> paramValuee) {
+               Map<String, String> params = new HashMap<>();
+               for (int i = 0; i < paramNames.size(); i++) {
+                       params.put(paramNames.get(i), paramValuee.get(i));
+               }
+               this.queryParams = params;
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/status/TrainModuleStatus.java b/TrainManager/src/main/java/hu/user/trainmanager/status/TrainModuleStatus.java
new file mode 100644 (file)
index 0000000..e1a6c94
--- /dev/null
@@ -0,0 +1,7 @@
+package hu.user.trainmanager.status;
+
+public enum TrainModuleStatus {
+       OK,
+       NOT_ACCESSIBLE,
+       RESPONSE_TIMEOUT
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStatus.java b/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStatus.java
new file mode 100644 (file)
index 0000000..485e738
--- /dev/null
@@ -0,0 +1,7 @@
+package hu.user.trainmanager.status;
+
+public enum TrainStatus {
+       OK,
+       NOT_ACCESSIBLE,
+       RESPONSE_TIMEOUT
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepName.java b/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepName.java
new file mode 100644 (file)
index 0000000..90a42fd
--- /dev/null
@@ -0,0 +1,11 @@
+package hu.user.trainmanager.status;
+
+public enum TrainStepName {
+
+       ENCODING,
+       FEATURE_ENGINEERING,
+       SAMPLING,
+       FITTING,
+       ESTIMATOR_PERSISTING,
+       METRIC_PERSISTING
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepState.java b/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepState.java
new file mode 100644 (file)
index 0000000..1a719bd
--- /dev/null
@@ -0,0 +1,15 @@
+package hu.user.trainmanager.status;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class TrainStepState {
+
+       private int trainTaskId;
+       private TrainStepName trainStepName;
+       private TrainStepStatus trainStepStatus;
+
+}
+
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepStatus.java b/TrainManager/src/main/java/hu/user/trainmanager/status/TrainStepStatus.java
new file mode 100644 (file)
index 0000000..f4d0285
--- /dev/null
@@ -0,0 +1,5 @@
+package hu.user.trainmanager.status;
+
+public enum TrainStepStatus {
+       READY, PREVIOUSLY_DONE, STARTED, FINISHED
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/status/TrainTaskState.java b/TrainManager/src/main/java/hu/user/trainmanager/status/TrainTaskState.java
new file mode 100644 (file)
index 0000000..921ade7
--- /dev/null
@@ -0,0 +1,8 @@
+package hu.user.trainmanager.status;
+
+public enum TrainTaskState {
+
+       STORED,
+       IN_PROGRESS,
+       DONE
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/DatabaseViewModel.java b/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/DatabaseViewModel.java
new file mode 100644 (file)
index 0000000..6676eed
--- /dev/null
@@ -0,0 +1,203 @@
+package hu.user.trainmanager.viewmodel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.zkoss.bind.annotation.Command;
+import org.zkoss.bind.annotation.Init;
+import org.zkoss.bind.annotation.NotifyChange;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zul.ListModelList;
+import org.zkoss.zul.Messagebox;
+
+import antlr.collections.impl.IntRange;
+import hu.user.trainmanager.dto.FraudCandidate;
+import hu.user.trainmanager.dto.TableProperties;
+import hu.user.trainmanager.dto.csv.ReducingAndEvaulationParameterRequestDto;
+import hu.user.trainmanager.dto.csv.ReducingAndEvaulationParameterResponseDto;
+import hu.user.trainmanager.service.CsvHandler;
+import hu.user.trainmanager.service.TrainClient;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+@Getter
+@Setter
+@Slf4j
+@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
+public class DatabaseViewModel {
+
+       @WireVariable private TrainClient trainClient;
+
+       @WireVariable private CsvHandler csvHandler;
+
+       private static final String SCHEMA_NAME = "schema_name";
+       private static final String TABLE_NAME = "table_name";
+       private static final String LIMIT = "limit";
+       private static final String OFFSET = "offset";
+       private static final int DEFAULT_SLIDER_MAX_VALUE = 10;
+
+       private String schema;
+       private List<String> schemas = new ArrayList<>();
+       private String table;
+       private List<String> tables = new ArrayList<>();
+       private TableProperties tableProperties;
+       private Integer recordNumber;
+       private ListModelList<Integer> offsetValues = new ListModelList<>();
+       private Integer offset;
+       private ListModelList<Integer> limitValues = new ListModelList<>();
+       private Integer limit;
+       private Map<String, String> fieldTypeByName = new HashMap<>();
+       private List<String> fieldNames = new ArrayList<>();
+       private List<String> fieldTypes = new ArrayList<>();
+
+       private ListModelList<List<Object>> records = new ListModelList<>();
+       private String actualFooter;
+       private List<String> fraudTypeFieldNames = new ArrayList<>();
+       private String fraudTypeFieldName;
+       private int desiredPositiveFraudNumber;
+       private int desiredNegativeFraudNumber;
+       private boolean enableAddDateTimeSuffix;
+       private boolean showTypeChecked;
+       private boolean showSplitChecked;
+       private Integer positiveCaseNumber = DEFAULT_SLIDER_MAX_VALUE;
+       private Integer negativeCaseNumber = DEFAULT_SLIDER_MAX_VALUE;
+
+       @Init
+       public void init() {
+               schemas = Arrays.stream(trainClient.getSchemas()).collect(Collectors.toList());
+//             ReducedAndEvaulationParameterRequestDto requestDto =
+//                             new ReducedAndEvaulationParameterRequestDto("card_10000_5", "transaction", "fraud", 5, 6,true);
+//             Map<Integer, Integer> result = trainClient.createReducedAndEvaulationTables(requestDto);
+//             System.out.println(result);
+       }
+
+       @Command
+       @NotifyChange("tables")
+       public void changeSchema() {
+               if (schema != null && schemas.contains(schema)) {
+                       tables = List.of(trainClient.getTables(Map.of(SCHEMA_NAME, schema)));
+               }
+       }
+
+       @Command
+       @NotifyChange("tables")
+       public void refreshTables() {
+               if (schema != null) {
+                       tables = List.of(trainClient.getTables(Map.of(SCHEMA_NAME, schema)));
+               }
+       }
+
+       @Command
+       @NotifyChange({"records", "recordNumber", "offsetValues", "limitValues", "fieldNames", "fieldTypes"})
+       public void changeTable() {
+               if (schema != null && table != null) {
+                       limit = null;
+                       offset = null;
+                       records.clear();
+                       limitValues.clear();
+                       offsetValues.clear();
+                       tableProperties = trainClient.getTableProperties(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table));
+                       List<FraudCandidate> fraudCandidates = tableProperties.getFraudCandidates();
+                       fraudTypeFieldNames = fraudCandidates.stream().map(e -> e.getName()).collect(Collectors.toList());
+                       recordNumber = tableProperties.getRecordNumber();
+                       if (recordNumber <= 100) {
+                               offsetValues = new ListModelList<>(List.of(0));
+                               limitValues =
+                                               new ListModelList<>(IntStream.range(1, recordNumber).boxed().collect(Collectors.toList()));
+                       } else {
+                               int groupNumber = recordNumber / 100;
+                               for (int i = 0; i < groupNumber; i++) {
+                                       offsetValues.add(i * 100);
+                               }
+                               limitValues = new ListModelList<>(IntStream.range(1, 100).boxed().collect(Collectors.toList()));
+                       }
+                       fieldTypeByName = trainClient.getFieldTypeByName(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table));
+                       fieldNames = Arrays.stream(
+                                                       trainClient.getFieldNameInOrdinalPosition(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table)))
+                                       .collect(Collectors.toList());
+                       fieldTypes = Arrays.stream(
+                                                       trainClient.getFieldTypeInOrdinalPosition(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table)))
+                                       .collect(Collectors.toList());
+                       if (limit != null && offset != null) {
+                               records = fillRecords();
+                       }
+               }
+       }
+
+       @Command
+       @NotifyChange("records")
+       public void changeOffset() {
+               if (limit != null && offset != null) {
+                       records = fillRecords();
+               }
+       }
+
+       @Command
+       @NotifyChange("records")
+       public void changeLimit() {
+               if (limit != null && offset != null) {
+                       records = fillRecords();
+               }
+       }
+
+       @Command
+       @NotifyChange({"positiveCaseNumber", "negativeCaseNumber"})
+       public void refreshFraudCaseNumbers() {
+               if (fraudTypeFieldName != null && schema != null && table != null) {
+                       tableProperties = trainClient.getTableProperties(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table));
+                       List<FraudCandidate> fraudCandidates = tableProperties.getFraudCandidates();
+                       for (FraudCandidate fraudCandidate : fraudCandidates) {
+                               if (fraudCandidate.getName().equals(fraudTypeFieldName)) {
+                                       positiveCaseNumber = fraudCandidate.getFraudNumber();
+                                       negativeCaseNumber = fraudCandidate.getNoFraudNumber();
+                               }
+                               log.info("Positive fraud case number: {}", positiveCaseNumber);
+                               log.info("Negative fraud case number: {}", negativeCaseNumber);
+                       }
+               }
+       }
+
+       @Command
+       public void splitDatabase() {
+               List<FraudCandidate> fraudCandidates = tableProperties.getFraudCandidates();
+               int positiveFraudNumber = 0;
+               int negativeFraudNumber = 0;
+               for (FraudCandidate fraudCandidate : fraudCandidates)
+                       if (fraudCandidate.getName().equals(fraudTypeFieldName)) {
+                               if (desiredPositiveFraudNumber < fraudCandidate.getFraudNumber()) {
+                                       positiveFraudNumber = desiredPositiveFraudNumber;
+                               } else {
+                                       positiveFraudNumber = fraudCandidate.getFraudNumber();
+                               }
+                               if (desiredNegativeFraudNumber < fraudCandidate.getNoFraudNumber()) {
+                                       negativeFraudNumber = desiredNegativeFraudNumber;
+                               } else {
+
+                                       negativeFraudNumber = fraudCandidate.getNoFraudNumber();
+                               }
+                       }
+               ReducingAndEvaulationParameterRequestDto requestDto =
+                               new ReducingAndEvaulationParameterRequestDto(schema, table, fraudTypeFieldName, positiveFraudNumber,
+                                               negativeFraudNumber, enableAddDateTimeSuffix);
+               ReducingAndEvaulationParameterResponseDto responseFromTrainModule =
+                               trainClient.createReducedAndEvaulationTables(requestDto);
+               String csvPath = trainClient.getEvaluationCsvDirectory();
+               String plannedCsvFileName = responseFromTrainModule.getPlannedCsvFileName();
+               Map<Integer, Integer> plannedCsvContent = responseFromTrainModule.getPlannedCsvContent();
+               csvHandler.createEvaulationCsvFile(csvPath, plannedCsvFileName, plannedCsvContent);
+               Messagebox.show("The table has been split and csv file created.");
+       }
+
+       private ListModelList<List<Object>> fillRecords() {
+               return new ListModelList<>(trainClient.getRecords(
+                               Map.of(SCHEMA_NAME, schema, TABLE_NAME, table, LIMIT, String.valueOf(limit), OFFSET,
+                                               String.valueOf(offset))));
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/InputTrainParameterViewModel.java b/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/InputTrainParameterViewModel.java
new file mode 100644 (file)
index 0000000..5704162
--- /dev/null
@@ -0,0 +1,535 @@
+package hu.user.trainmanager.viewmodel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.zkoss.bind.BindUtils;
+import org.zkoss.bind.ValidationContext;
+import org.zkoss.bind.Validator;
+import org.zkoss.bind.annotation.BindingParam;
+import org.zkoss.bind.annotation.Command;
+import org.zkoss.bind.annotation.ContextParam;
+import org.zkoss.bind.annotation.ContextType;
+import org.zkoss.bind.annotation.Init;
+import org.zkoss.bind.annotation.NotifyChange;
+import org.zkoss.bind.validator.AbstractValidator;
+import org.zkoss.zk.ui.Component;
+import org.zkoss.zk.ui.Desktop;
+import org.zkoss.zk.ui.Executions;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zul.ListModelList;
+import org.zkoss.zul.Messagebox;
+
+import hu.user.trainmanager.dto.EncodingAndFeatureEngineeringParameters;
+import hu.user.trainmanager.dto.FeatureEnginneringParameters;
+import hu.user.trainmanager.dto.FieldProperty;
+import hu.user.trainmanager.dto.FraudCandidate;
+import hu.user.trainmanager.dto.TableProperties;
+import hu.user.trainmanager.dto.TrainTaskParameters;
+import hu.user.trainmanager.dto.display.FieldNameAndEncodingType;
+import hu.user.trainmanager.service.TrainClient;
+import hu.user.trainmanager.status.TrainTaskState;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
+public class InputTrainParameterViewModel {
+
+       private static final String GET_CPU = "get_cpu";
+       private static final String GET_SCHEMAS = "get_schemas";
+       private static final String GET_TABLES = "get_tables";
+       private static final String SCHEMA_NAME = "schema_name";
+       private static final String TABLE_NAME = "table_name";
+       private static final String TIME_BASE_FIELD_NAME = "time_base_field_name";
+       private static final String GET_TABLE_PROPERTY = "get_table_property";
+       private static final String GET_RECORDS = "get_records";
+       private static final String LIMIT = "limit";
+       private static final String OFFSET = "offset";
+       private static final String POST_E_AND_FE_PARAMETERS = "new_e_and_fe_parameters";
+       private static final String GET_E_AND_FE_PARAMETERS = "get_e_and_fe_parameters";
+       private static final String GET_FEATURE_SELECTOR = "get_feature_selector";
+       private static final String GET_SAMPLER = "get_sampler";
+       private static final String GET_SCALER = "get_scaler";
+       private static final String GET_MODEL = "get_model";
+       private static final String NEW_TRAIN_TASK = "new_train_task";
+       private static final String GET_TRAIN_TASK = "get_train_task";
+       private static final String FIT = "fit";
+       private static final String GET_METRICS = "get_metrics";
+       private static final String GET_ESTIMATOR = "get_estimator";
+       private static final String ESTIMATOR_ID = "estimator_id";
+       private static final String ID = "id";
+       private static final String INT = "int";
+       private static final String FLOAT = "float";
+       private static final String DOUBLE = "double";
+       private static final String VARCHAR = "varchar";
+       private static final String DATE_TIME = "datetime";
+       private static final String JULIAN = "julian";
+       private static final String LABEL_ENCODER = "label_encoder";
+       private static final String FRAUD_TYPE = "fraud_type";
+       private static final String OMIT = "omit";
+       private static final String CARD_NUMBER = "card_number";
+
+       private static final List<String> APPLICABLE_TYPES_FOR_FE = List.of(INT, FLOAT, DOUBLE);
+
+       @WireVariable private TrainClient trainClient;
+
+       private String schema;
+       private String table;
+       private String featureSelector;
+       private String sampler;
+       private String scaler;
+       private String model;
+
+       private int cpuCore;
+
+       private List<String> schemas = new ArrayList<>();
+       private List<String> tables = new ArrayList<>();
+       private TableProperties tableProperties;
+       private Map<String, String> encodingParameters = new HashMap<>();
+
+       private boolean enabledFeatureEngineering;
+       private FeatureEnginneringParameters featureEnginneringParameters;
+
+       private List<String> eligibleTimeBaseFields;
+       private String chosenTimeBaseField;
+       private List<String> fieldCandidatesForFeatureEngineering = new ArrayList<>();
+       private String chosenFeatureField;
+       private List<String> fields = new ArrayList<>();
+       private int fieldIndex;
+
+       private String currentField;
+
+       private String currentEncodingType;
+       private ListModelList<String> currentApplicableEncodingTypes = new ListModelList<>();
+
+       private List<String> availableEncodingTypes = new ArrayList<>();
+
+       private boolean disabledNextEncodingTypeButton;
+       private boolean disabledFinishEncodingTypeButton;
+       private Integer encodingAndFeatureEngineeringId;
+       private Map<String, String> featureSelectorById = new HashMap<>();
+       private String featureSelectorName;
+       private Map<String, String> samplerById = new HashMap<>();
+
+       private String samplerName;
+       private Map<String, String> scalerById = new HashMap<>();
+       private String scalerName;
+       private Map<String, String> modelById = new HashMap<>();
+       private String modelName;
+       private Integer testSizePercent;
+       private Integer retroSpectiveDay;
+       private Integer maxRetroSpectiveDay;
+
+       private List<Integer> intervals = new ArrayList<>();
+       private String presentedIntervals;
+
+       private List<EncodingAndFeatureEngineeringParameters> storedEncodingAndFeatureEngineeringParameters;
+
+       private EncodingAndFeatureEngineeringParameters selectedStoredEncodingAndFeatureEngineeringParameter;
+
+       private List<FieldNameAndEncodingType> displayedEncodingParameters;
+
+       private String displayedChosenFeatureField;
+       private String displayedRetroSpectiveIntervals;
+
+       private String trainTaskName;
+       private boolean allEncodingParametersSet;
+       private Validator trainTaskNameValidator = getTrainTaskNameValidator();
+       private List<String> columnsContainOnlyDistinctValues = new ArrayList<>();
+       private String uniqueFieldName;
+       private String encodingErrorMessage;
+       private String featureEngineeringErrorMessage;
+
+       @Init
+       public void init(@ContextParam(ContextType.COMPONENT) Component component) {
+               schemas = List.of(trainClient.getSchemas());
+               featureSelectorById = trainClient.getFeatureSelectors();
+               samplerById = trainClient.getSamplers();
+               scalerById = trainClient.getScalers();
+               modelById = trainClient.getModels();
+               disabledNextEncodingTypeButton = true;
+               disabledFinishEncodingTypeButton = true;
+       }
+
+       private void clearPipeLineParameters() {
+               featureSelectorName = null;
+               samplerName = null;
+               scalerName = null;
+               modelName = null;
+               testSizePercent = null;
+               trainTaskName = null;
+               BindUtils.postNotifyChange(null, null, this, "featureSelectorName");
+               BindUtils.postNotifyChange(null, null, this, "samplerName");
+               BindUtils.postNotifyChange(null, null, this, "scalerName");
+               BindUtils.postNotifyChange(null, null, this, "modelName");
+               BindUtils.postNotifyChange(null, null, this, "testSizePercent");
+               BindUtils.postNotifyChange(null, null, this, "trainTaskName");
+
+       }
+
+       public ListModelList<String> getfeatureSelectors() {
+               return new ListModelList<>(getListFromMap(featureSelectorById));
+       }
+
+       public List<String> getSamplers() {
+               return getListFromMap(samplerById);
+       }
+
+       public List<String> getScalers() {
+               return getListFromMap(scalerById);
+       }
+
+       public List<String> getModels() {
+               return getListFromMap(modelById);
+       }
+
+       public List<String> getListFromMap(Map<String, String> input) {
+               return input.entrySet().stream().map(e -> e.getValue()).collect(Collectors.toList());
+       }
+
+       private String getKeyFromMapByValue(Map<String, String> map, String value) {
+               String result =
+                               map.entrySet().stream().filter(e -> e.getValue().equals(value)).map(e -> e.getKey()).findFirst().get();
+               return result;
+       }
+
+       @Command
+       @NotifyChange("tables")
+       public void changeSchema() {
+               if (schema != null && schemas.contains(schema)) {
+                       tables = List.of(trainClient.getTables(Map.of(SCHEMA_NAME, schema)));
+               }
+       }
+
+       @Command
+       @NotifyChange({"fieldCandidatesForFeatureEngineering", "fields", "fieldIndex", "fieldIndex", "currentField",
+                       "disabledNextEncodingTypeButton", "disabledFinishEncodingTypeButton", "eligibleTimeBaseFields"})
+       public void changeTable() {
+               if (schema != null && table != null) {
+                       tableProperties = trainClient.getTableProperties(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table));
+                       columnsContainOnlyDistinctValues =
+                                       trainClient.getColumnsContainOnlyDistinctValues(Map.of(SCHEMA_NAME, schema, TABLE_NAME, table));
+                       encodingParameters = new HashMap<>();
+                       collectEligibleTimeBaseFields();
+                       collectFieldCandidatesForFeatureEngineering();
+                       collectFieldNames();
+               }
+       }
+
+       @Command
+       public void changeUniqueField() {
+               if (chosenTimeBaseField != null) {
+                       initBeforeChooseEncodingTypes();
+               }
+       }
+
+       @Command
+       public void changeTimeBaseField() {
+               if (uniqueFieldName != null) {
+                       initBeforeChooseEncodingTypes();
+               }
+               maxRetroSpectiveDay = trainClient.getMaxRetroSpectiveDay(
+                               Map.of(SCHEMA_NAME, schema, TABLE_NAME, table, TIME_BASE_FIELD_NAME, chosenTimeBaseField));
+       }
+
+       private void initBeforeChooseEncodingTypes() {
+               fieldIndex = 0;
+               setCurrentFieldValue(0);
+               disabledNextEncodingTypeButton = false;
+               disabledFinishEncodingTypeButton = true;
+               BindUtils.postNotifyChange(null, null, this, "disabledNextEncodingTypeButton");
+               BindUtils.postNotifyChange(null, null, this, "disabledFinishEncodingTypeButton");
+       }
+
+       private void collectEligibleTimeBaseFields() {
+               eligibleTimeBaseFields =
+                               tableProperties.getFields().stream().filter(e -> e.getType().equals(DATE_TIME)).map(e -> e.getName())
+                                               .collect(Collectors.toList());
+       }
+
+       private void collectFieldCandidatesForFeatureEngineering() {
+               if (tableProperties != null) {
+                       List<FieldProperty> fieldProperties = tableProperties.getFields();
+                       fieldCandidatesForFeatureEngineering =
+                                       fieldProperties.stream().filter(e -> APPLICABLE_TYPES_FOR_FE.contains(e.getType()))
+                                                       .map(e -> e.getName()).collect(Collectors.toList());
+               }
+       }
+
+       private void collectFieldNames() {
+               if (tableProperties != null) {
+                       fields = tableProperties.getFields().stream().map(e -> e.getName()).collect(Collectors.toList());
+                       if (tableProperties.getPrimaryKey() != null) {
+                               fields.remove(tableProperties.getPrimaryKey());
+                       }
+               }
+       }
+
+       private void setCurrentFieldValue(int fieldIndex) {
+               currentField = fields.get(fieldIndex);
+               BindUtils.postNotifyChange(null, null, this, "currentField");
+               setCurrentApplicableEncodingTypes();
+       }
+
+       public void setCurrentApplicableEncodingTypes() {
+               Optional<FieldProperty> optionalFieldProperty =
+                               tableProperties.getFields().stream().filter(e -> e.getName().equals(currentField)).findFirst();
+               if (optionalFieldProperty.isPresent()) {
+                       FieldProperty fieldProperty = optionalFieldProperty.get();
+                       String type = fieldProperty.getType();
+                       List<String> otherApplicableEncodingTypes = List.of(fieldProperty.getOtherApplicableEncodingTypes());
+                       currentApplicableEncodingTypes.add(OMIT);
+                       switch (type) {
+                       case DATE_TIME:
+                               currentApplicableEncodingTypes.add(JULIAN);
+                               break;
+                       case VARCHAR:
+                               currentApplicableEncodingTypes.add(LABEL_ENCODER);
+                               currentApplicableEncodingTypes.addAll(otherApplicableEncodingTypes);
+                               break;
+                       case INT:
+                               currentApplicableEncodingTypes.add(INT);
+                               List<FraudCandidate> fraudCandidates = tableProperties.getFraudCandidates();
+                               List<String> fraudCandidateFieldNames =
+                                               fraudCandidates.stream().map(e -> e.getName()).collect(Collectors.toList());
+                               if (fraudCandidateFieldNames.contains(fieldProperty.getName())) {
+                                       currentApplicableEncodingTypes.add(FRAUD_TYPE);
+                               }
+                               break;
+                       case FLOAT:
+                               currentApplicableEncodingTypes.add(FLOAT);
+                               break;
+                       }
+               }
+       }
+
+       @Command
+       @NotifyChange({"disabledNextEncodingTypeButton", "disabledFinishEncodingTypeButton",
+                       "currentApplicableEncodingTypes", "currentEncodingType", "currentField"})
+       public void saveCurrentFieldAndEncodingTypeAndGetNextField() {
+               if (currentEncodingType != null && !currentEncodingType.isEmpty()) {
+                       saveCurrentFieldAndEncodingType();
+                       fieldIndex++;
+               }
+               if (fieldIndex == fields.size() - 1) {
+                       disabledNextEncodingTypeButton = true;
+                       disabledFinishEncodingTypeButton = false;
+               }
+               currentApplicableEncodingTypes.clear();
+               currentEncodingType = null;
+               currentField = null;
+//             BindUtils.postNotifyChange(null, null, this, "currentApplicableEncodingTypes");
+//             BindUtils.postNotifyChange(null, null, this, "currentEncodingType");
+               if (fieldIndex <= fields.size() - 1) {
+                       setCurrentFieldValue(fieldIndex);
+               }
+       }
+
+       @Command
+       @NotifyChange({"disabledFinishEncodingTypeButton", "currentApplicableEncodingTypes", "currentEncodingType",
+                       "currentField", "encodingErrorMessage"})
+       public void saveLastFieldAndEncodingType() {
+               if (currentEncodingType != null && !currentEncodingType.isEmpty()) {
+                       saveCurrentFieldAndEncodingType();
+                       disabledFinishEncodingTypeButton = true;
+                       currentApplicableEncodingTypes.clear();
+                       currentEncodingType = null;
+                       currentField = null;
+
+                       if (!encodingParameters.values().contains(FRAUD_TYPE)) {
+                               encodingErrorMessage = "Fraud type field is missing";
+                       }
+                       if (encodingParameters.get(chosenTimeBaseField).equals(OMIT)) {
+                               encodingErrorMessage = "Time base field has been omitted";
+                       }
+                       if (encodingParameters.keySet().contains(CARD_NUMBER) && encodingParameters.get(CARD_NUMBER).equals(OMIT)) {
+                               encodingParameters.put(CARD_NUMBER, LABEL_ENCODER);
+                       }
+                       System.out.println();
+               }
+       }
+
+       public void saveCurrentFieldAndEncodingType() {
+               encodingParameters.put(currentField, currentEncodingType);
+               if (encodingParameters.size() == fields.size()) {
+                       allEncodingParametersSet = true;
+               }
+       }
+
+       @Command
+       @NotifyChange("encodingErrorMessage")
+       public void clearEncodingParametersAfterError() {
+               encodingErrorMessage = null;
+               clearEncodingParameters();
+               if (schema != null && table != null && uniqueFieldName != null && chosenTimeBaseField != null) {
+                       setCurrentFieldValue(0);
+                       fieldIndex = 0;
+               }
+               disabledNextEncodingTypeButton = false;
+               BindUtils.postNotifyChange(null, null, this, "disabledNextEncodingTypeButton");
+       }
+
+       @Command
+       @NotifyChange("presentedIntervals")
+       public void addDayToIntervals() {
+               if (!intervals.contains(retroSpectiveDay) && retroSpectiveDay < maxRetroSpectiveDay) {
+                       intervals.add(retroSpectiveDay);
+                       presentedIntervals = intervals.stream().map(e -> String.valueOf(e)).collect(Collectors.joining("; "));
+               }
+       }
+
+       @Command
+       @NotifyChange("featureEngineeringErrorMessage")
+       public void checkIfSelectedFeatureFieldExist() {
+               if (encodingParameters.keySet().contains(chosenFeatureField) && encodingParameters.get(chosenFeatureField)
+                               .equals(OMIT)) {
+                       featureEngineeringErrorMessage = "The field has been omitted";
+               } else {
+                       featureEngineeringErrorMessage = null;
+               }
+       }
+
+       public void saveEncodingAndFeatureEngineering() {
+               int[] intervalsAsArray = new int[intervals.size()];
+               for (int i = 0; i < intervals.size(); i++) {
+                       intervalsAsArray[i] = intervals.get(i);
+               }
+               filterEncodingParameters();
+               FeatureEnginneringParameters feParameters = null;
+               if (enabledFeatureEngineering && !chosenFeatureField.isEmpty() && intervalsAsArray.length > 0) {
+                       feParameters = new FeatureEnginneringParameters(chosenFeatureField, intervalsAsArray);
+               }
+               EncodingAndFeatureEngineeringParameters efeParameters =
+                               new EncodingAndFeatureEngineeringParameters(schema, table, chosenTimeBaseField, encodingParameters,
+                                               feParameters);
+               encodingAndFeatureEngineeringId = trainClient.addEncodingAndFeatureEngineeringParameters(efeParameters);
+       }
+
+       @Command
+       public void saveTrainTask() {
+               saveEncodingAndFeatureEngineering();
+               int featureSelectorCode = Integer.parseInt(getKeyFromMapByValue(featureSelectorById, featureSelectorName));
+               int samplerCode = Integer.parseInt(getKeyFromMapByValue(samplerById, samplerName));
+               int scalerCode = Integer.parseInt(getKeyFromMapByValue(scalerById, scalerName));
+               int modelCode = Integer.parseInt(getKeyFromMapByValue(modelById, modelName));
+               double testSize = testSizePercent * 0.01;
+               TrainTaskParameters trainTaskParameters =
+                               new TrainTaskParameters(trainTaskName, uniqueFieldName, encodingAndFeatureEngineeringId,
+                                               featureSelectorCode, samplerCode, scalerCode, modelCode, testSize, TrainTaskState.STORED);
+               int trainTaskId = trainClient.addTrainTask(trainTaskParameters);
+               Messagebox.show("Train Task persisted, train task id: " + trainTaskId);
+               clearInputParametersOnForm();
+               BindUtils.postGlobalCommand(null, null, "automaticRefreshTrainTasks", null);
+       }
+
+       private void clearInputParametersOnForm() {
+               clearEncodingParameters();
+               clearFeatureEngineeringParameters();
+               clearPipeLineParameters();
+               if (schema != null && table != null && uniqueFieldName != null && chosenTimeBaseField != null) {
+                       initBeforeChooseEncodingTypes();
+               }
+
+       }
+
+       @Command
+       @NotifyChange("allEncodingParametersSet")
+       public void clearAllEncodingParameters() {
+               clearDatabaseParameters();
+               clearSpecificProperties();
+               clearEncodingParameters();
+               clearFeatureEngineeringParameters();
+               clearPipeLineParameters();
+               fieldIndex = 0;
+       }
+
+       @Command
+       @NotifyChange("presentedIntervals")
+       public void clearPresentedIntervals() {
+               intervals.clear();
+               presentedIntervals = null;
+       }
+
+       private void clearDatabaseParameters() {
+               schema = null;
+               table = null;
+               BindUtils.postNotifyChange(null, null, this, "schema");
+               BindUtils.postNotifyChange(null, null, this, "table");
+       }
+
+       private void clearSpecificProperties() {
+               chosenTimeBaseField = null;
+               uniqueFieldName = null;
+               BindUtils.postNotifyChange(null, null, this, "chosenTimeBaseField");
+               BindUtils.postNotifyChange(null, null, this, "uniqueFieldName");
+       }
+
+       private void clearEncodingParameters() {
+               currentField = null;
+               currentEncodingType = null;
+               currentApplicableEncodingTypes.clear();
+               encodingParameters.clear();
+               allEncodingParametersSet = false;
+               disabledNextEncodingTypeButton = true;
+               disabledFinishEncodingTypeButton = true;
+               allEncodingParametersSet = false;
+               BindUtils.postNotifyChange(null, null, this, "currentField");
+               BindUtils.postNotifyChange(null, null, this, "currentEncodingType");
+               BindUtils.postNotifyChange(null, null, this, "currentApplicableEncodingTypes");
+               BindUtils.postNotifyChange(null, null, this, "disabledNextEncodingTypeButton");
+               BindUtils.postNotifyChange(null, null, this, "disabledFinishEncodingTypeButton");
+       }
+
+       private void clearFeatureEngineeringParameters() {
+               enabledFeatureEngineering = false;
+               retroSpectiveDay = null;
+               chosenFeatureField = null;
+               presentedIntervals = null;
+               intervals.clear();
+               featureEngineeringErrorMessage = null;
+               BindUtils.postNotifyChange(null, null, this, "enabledFeatureEngineering");
+               BindUtils.postNotifyChange(null, null, this, "retroSpectiveDay");
+               BindUtils.postNotifyChange(null, null, this, "chosenFeatureField");
+               BindUtils.postNotifyChange(null, null, this, "presentedIntervals");
+               BindUtils.postNotifyChange(null, null, this, "featureEngineeringErrorMessage");
+       }
+
+       private void filterEncodingParameters() {
+               List<FieldProperty> fieldProperties = tableProperties.getFields();
+               for (FieldProperty property : fieldProperties) {
+                       String fieldName = property.getName();
+                       if (property.getType().equals(INT) || property.getType().equals(FLOAT) && encodingParameters.containsKey(
+                                       fieldName)) {
+                               if (encodingParameters.get(fieldName).equals(INT) || encodingParameters.get(fieldName).equals(FLOAT)) {
+                                       encodingParameters.remove(fieldName);
+                               }
+                       }
+               }
+       }
+
+       public Validator getTrainTaskNameValidator() {
+               return new AbstractValidator() {
+                       @Override
+                       public void validate(ValidationContext validationContext) {
+                               String plannedTrainTaskName = (String) validationContext.getProperty().getValue();
+                               Map<Integer, TrainTaskParameters> trainTaskParametersById =
+                                               trainClient.getTrainTask(Collections.emptyMap());
+                               List<String> trainTaskNames =
+                                               trainTaskParametersById.entrySet().stream().map(e -> e.getValue().getTrainTaskName())
+                                                               .collect(Collectors.toList());
+                               if (trainTaskNames.contains(plannedTrainTaskName)) {
+                                       addInvalidMessage(validationContext, "This name already exists");
+                               }
+                       }
+               };
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/MetricsViewModel.java b/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/MetricsViewModel.java
new file mode 100644 (file)
index 0000000..d8bce26
--- /dev/null
@@ -0,0 +1,167 @@
+package hu.user.trainmanager.viewmodel;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.zkoss.bind.annotation.BindingParam;
+import org.zkoss.bind.annotation.Command;
+import org.zkoss.bind.annotation.GlobalCommand;
+import org.zkoss.bind.annotation.Init;
+import org.zkoss.bind.annotation.NotifyChange;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.Wire;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zul.Filedownload;
+import org.zkoss.zul.Messagebox;
+
+import hu.user.trainmanager.dto.DirectoryProperty;
+import hu.user.trainmanager.dto.FileProperty;
+import hu.user.trainmanager.dto.csv.CsvContentDto;
+import hu.user.trainmanager.dto.csv.CsvDataHolder;
+import hu.user.trainmanager.service.CsvHandler;
+import hu.user.trainmanager.service.MetricsCsvDataBuilder;
+import hu.user.trainmanager.service.TrainClient;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
+public class MetricsViewModel {
+       private String homeDirectory;
+       @WireVariable private TrainClient trainClient;
+       @WireVariable private MetricsCsvDataBuilder metricsCsvDataBuilder;
+       @WireVariable private CsvHandler csvHandler;
+
+//     private List<EstimatorMetricsDto> estimatorMetrics = new ArrayList<>();
+
+       private String modalWindow;
+       private String fullPath;
+       private List<DirectoryProperty> directoryProperties = new ArrayList<>();
+       private List<FileProperty> fileProperties = new ArrayList<>();
+       private String trainTaskCsvFileName;
+       private String metricsCsvFileName;
+       private boolean enabledTrainTaskCsvDateTimeSuffix;
+       private boolean enabledMetricsCsvDateTimeSuffix;
+       private File selectedDirectory;
+       private CsvContentDto metricsCsvContentDto;
+
+       @Init
+       public void init() {
+               homeDirectory = trainClient.getHomeDirectory();
+               File root = new File(homeDirectory);
+               selectedDirectory = root;
+               directoryProperties = getDirectoryProperties(root);
+               fullPath = root.getAbsolutePath();
+               metricsCsvContentDto = trainClient.getMetricsCsvContent();
+       }
+
+       @Command
+       @GlobalCommand
+       @NotifyChange("metricsCsvContentDto")
+       public void refreshMetrics() {
+               metricsCsvContentDto = trainClient.getMetricsCsvContent();
+       }
+
+
+//     @Command
+//     @NotifyChange("modalWindow")
+//     public void showSaveMetricsWindow() {
+//             modalWindow = "~./zul/metricsSave.zul";
+//             System.out.println(modalWindow);
+//     }
+
+       @Command
+       public void saveMetricsCsv() {
+               System.out.println("on");
+               CsvDataHolder csvDataHolder = metricsCsvDataBuilder.getCsvContent();
+               String filePath = "/home/tomi/log/csv.log";
+               csvHandler.writeDataLineByLine(filePath, csvDataHolder);
+       }
+
+//     @Command
+//     public void save() {
+//             try {
+//                     File file = new File("/home/tomi/tmp/AWS_Konyv.pdf");
+//                     Filedownload.save(file, "application/pdf");
+//             } catch (FileNotFoundException e) {
+//                     System.out.println("Not found");
+//             }
+//     }
+
+       @Command
+       @NotifyChange({"directoryProperties", "fileProperties", "fullPath"})
+       public void changeDirectory(@BindingParam("param") String selectedDirectoryName) {
+               selectedDirectory = new File(selectedDirectoryName);
+               directoryProperties.clear();
+               directoryProperties.add(new DirectoryProperty("..", selectedDirectory.getParentFile()));
+               directoryProperties.addAll(getDirectoryProperties(selectedDirectory));
+               fileProperties = getFileProperties(selectedDirectory);
+               fullPath = selectedDirectoryName;
+       }
+
+       @Command
+       public void downloadSelectedCsvFile(@BindingParam("param") String selectedFileName) {
+               try {
+                       File selectedFile = new File(selectedFileName);
+                       Filedownload.save(selectedFile, null);
+               } catch (FileNotFoundException e) {
+                       System.out.println("Not found");
+               }
+       }
+
+       @Command
+       @NotifyChange("fileProperties")
+       public void deleteSelectedCsvFile(@BindingParam("param") String selectedFileName) {
+               File selectedFile = new File(selectedFileName);
+               try {
+                       boolean result = Files.deleteIfExists(Paths.get(selectedFileName));
+                       if (result) {
+                               Messagebox.show("File deleted");
+                       } else {
+                               Messagebox.show("Unable to delete the file");
+                       }
+               } catch (IOException e) {
+                       System.out.println("exception");
+               }
+               Optional<FileProperty> optionalFileProperty =
+                               fileProperties.stream().filter(e -> e.getFile().equals(selectedFile)).findFirst();
+               optionalFileProperty.ifPresent(e -> fileProperties.remove(e));
+       }
+
+       @Command
+       @NotifyChange({"fileProperties", "trainTaskCsvFileName"})
+       public void createTrainTaskCsvFile() {
+               csvHandler.createTrainTaskCsvFile(fullPath, trainTaskCsvFileName, enabledTrainTaskCsvDateTimeSuffix);
+               fileProperties = getFileProperties(selectedDirectory);
+               trainTaskCsvFileName = "";
+       }
+
+       @Command
+       @NotifyChange({"fileProperties", "metricsCsvFileName"})
+       public void createMetricsCsvFile() {
+               csvHandler.createMetricsCsvFile(fullPath, metricsCsvFileName, enabledMetricsCsvDateTimeSuffix);
+               fileProperties = getFileProperties(selectedDirectory);
+               metricsCsvFileName = "";
+
+       }
+
+       private List<DirectoryProperty> getDirectoryProperties(File directory) {
+               return Arrays.stream(directory.listFiles()).filter(e -> e.isDirectory() && !e.isHidden())
+                               .map(e -> new DirectoryProperty(e.getName(), new File(e.toURI()))).collect(Collectors.toList());
+       }
+
+       private List<FileProperty> getFileProperties(File directory) {
+               return Arrays.stream(directory.listFiles()).filter(e -> e.isFile()).map(e -> new FileProperty(e, e.getName()))
+                               .collect(Collectors.toList());
+       }
+}
diff --git a/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/ProgressPresenter.java b/TrainManager/src/main/java/hu/user/trainmanager/viewmodel/ProgressPresenter.java
new file mode 100644 (file)
index 0000000..ba39ceb
--- /dev/null
@@ -0,0 +1,8 @@
+package hu.user.trainmanager.viewmodel;
+
+import hu.user.trainmanager.event.TrainTaskEvent;
+
+public interface ProgressPresenter {
+
+       void changeUi(TrainTaskEvent event);
+}
diff --git a/TrainManager/src/main/resources/META-INF/spring-devtools.properties b/TrainManager/src/main/resources/META-INF/spring-devtools.properties
new file mode 100644 (file)
index 0000000..0161707
--- /dev/null
@@ -0,0 +1 @@
+restart.include.zklibs=/z[\\w]+-[\\w\\d-\.]+\.jar
diff --git a/TrainManager/src/main/resources/application.properties b/TrainManager/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..d0f1f5f
--- /dev/null
@@ -0,0 +1,6 @@
+server.port=8084
+zk.homepage=start
+zk.zul-view-resolver-prefix=/zul
+spring.devtools.restart.exclude=web/zul/**
+home.directory=/home/tomi
+evaluation.csv.directory=/home/tomi/csv
diff --git a/TrainManager/src/main/resources/application.yml b/TrainManager/src/main/resources/application.yml
new file mode 100644 (file)
index 0000000..cb146a6
--- /dev/null
@@ -0,0 +1,46 @@
+spring:
+  profiles:
+    default: "dev"
+  application:
+    name: TrainManager
+  datasource:
+    url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/common_fraud?serverTimezone=UTC
+    username: root
+    password: pwd
+logging:
+  level:
+    root: info
+  pattern:
+    console: = %d{yyyy-MM-dd HH:mm:ss} - %msg%n
+    file: =%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+  file:
+    name: /home/tomi/log/${spring.application.name}.log
+server:
+  port: 8086
+trainUrl: http://localhost:8085
+showProgressRepeatTime: 500
+trainLivenessRepeatTime: 100
+
+---
+spring:
+  config:
+    activate:
+      on-profile: dockerized
+#  jpa:
+#    show-sql: true
+#    generate-ddl: true
+#    hibernate:
+#      formar_sql: true
+logging:
+  pattern:
+    console: = %d{yyyy-MM-dd HH:mm:ss} - %msg%n
+    file: =%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+  file:
+    name: /opt/app/log/${spring.application.name}.log
+server:
+  port: 8086
+trainUrl: http://train:8085
+showProgressRepeatTime: 500
+trainLivenessRepeatTime: 100
+
+---
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/metainfo.zk/lang-addon.xml b/TrainManager/src/main/resources/metainfo.zk/lang-addon.xml
new file mode 100644 (file)
index 0000000..02e4b61
--- /dev/null
@@ -0,0 +1,10 @@
+<language-addon>
+    <addon-name>zkspringboot-demo-addon</addon-name>
+    <version>1.0</version>
+    <language-name>xul/html</language-name>
+
+    <depends>zkmax</depends>
+
+    <stylesheet href="/css/static-globalstyles.css" type="text/css"/>
+    <stylesheet href="~./css/web-globalstyles.css" type="text/css"/>
+</language-addon>
diff --git a/TrainManager/src/main/resources/metainfo.zk/zk-dev.xml b/TrainManager/src/main/resources/metainfo.zk/zk-dev.xml
new file mode 100644 (file)
index 0000000..7952d75
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<zk>
+    <system-config>
+        <disable-zscript>false</disable-zscript>
+    </system-config>
+    <!-- show debug information -->
+    <client-config>
+        <debug-js>true</debug-js>
+    </client-config>
+    <library-property>
+        <name>org.zkoss.zk.ui.versionInfo.enabled</name>
+        <value>true</value>
+    </library-property>
+    <!-- disable various caches -->
+    <library-property>
+        <name>org.zkoss.zk.ZUML.cache</name>
+        <value>false</value>
+    </library-property>
+    <library-property>
+        <name>org.zkoss.zk.WPD.cache</name>
+        <value>false</value>
+    </library-property>
+    <library-property>
+        <name>org.zkoss.zk.WCS.cache</name>
+        <value>false</value>
+    </library-property>
+    <library-property>
+        <name>org.zkoss.web.classWebResource.cache</name>
+        <value>false</value>
+    </library-property>
+    <library-property>
+        <name>org.zkoss.util.label.cache</name>
+        <value>false</value>
+    </library-property>
+</zk>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/metainfo.zk/zk.xml b/TrainManager/src/main/resources/metainfo.zk/zk.xml
new file mode 100644 (file)
index 0000000..d0a9268
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<zk>
+    <config-name>zkspringboot-demo</config-name>
+    <library-property>
+        <name>org.zkoss.zul.nativebar</name>
+        <value>true</value>
+    </library-property>
+    <library-property>
+        <name>org.zkoss.zul.image.preload</name>
+        <value>true</value>
+    </library-property>
+<!--    <library-property>-->
+<!--        <name>org.zkoss.zk.config.path</name>-->
+<!--        <value>zk-dev.xml</value>-->
+<!--    </library-property>-->
+
+    <!--enable websocket support in ZK 8.5.1 (ZK-3799)  -->
+    <!--    <listener>-->
+    <!--        <listener-class>org.zkoss.zkmax.au.websocket.WebSocketWebAppInit</listener-class>-->
+    <!--    </listener>-->
+
+    <!--    <richlet>-->
+    <!--        <richlet-name>ExampleRichlet</richlet-name>&lt;!&ndash; your preferred name &ndash;&gt;-->
+    <!--        <richlet-class>org.zkoss.zkspringboot.demo.richlet.ExampleRichlet</richlet-class>&lt;!&ndash; your class name, of course &ndash;&gt;-->
+    <!--    </richlet>-->
+    <!--    <richlet-mapping>-->
+    <!--        <richlet-name>ExampleRichlet</richlet-name>-->
+    <!--        <url-pattern>/richlet/example</url-pattern>-->
+    <!--    </richlet-mapping>-->
+</zk>
diff --git a/TrainManager/src/main/resources/web/css/mySpecial.css b/TrainManager/src/main/resources/web/css/mySpecial.css
new file mode 100644 (file)
index 0000000..c378172
--- /dev/null
@@ -0,0 +1,4 @@
+.bg {
+    background-image: url("plane.jpg");
+    background-size: cover;
+}
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/css/zk-admin-bootstrap-theme.css b/TrainManager/src/main/resources/web/css/zk-admin-bootstrap-theme.css
new file mode 100644 (file)
index 0000000..fb4a93e
--- /dev/null
@@ -0,0 +1,7 @@
+/* Created with Themestr.app */
+/*! `Custom` Bootstrap 4 theme *//*!
+ * Bootstrap v4.3.1 (https://getbootstrap.com/)
+ * Copyright 2011-2019 The Bootstrap Authors
+ * Copyright 2011-2019 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */:root{--blue: #007bff;--indigo: #6610f2;--purple: #6f42c1;--pink: #e83e8c;--red: #dc3545;--orange: #fd7e14;--yellow: #ffc107;--green: #28a745;--teal: #20c997;--cyan: #17a2b8;--white: #fff;--gray: #6c757d;--gray-dark: #343a40;--primary: #0093f9;--secondary: #B2DEFD;--success: #15CAB4;--info: #9665ed;--warning: #ffa516;--danger: #ff4051;--light: #f8f9fa;--dark: #343a40;--breakpoint-xs: 0;--breakpoint-sm: 576px;--breakpoint-md: 768px;--breakpoint-lg: 992px;--breakpoint-xl: 1200px;--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}*,*::before,*::after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0 !important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0093f9;text-decoration:none;background-color:transparent}a:hover{color:#0066ad;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):hover,a:not([href]):not([tabindex]):focus{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button:not(:disabled),[type="button"]:not(:disabled),[type="reset"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{padding:0;border-style:none}input[type="radio"],input[type="checkbox"]{box-sizing:border-box;padding:0}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{outline-offset:-2px;-webkit-appearance:none}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none !important}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:2.5rem}h2,.h2{font-size:2rem}h3,.h3{font-size:1.75rem}h4,.h4{font-size:1.5rem}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,0.1)}small,.small{font-size:80%;font-weight:400}mark,.mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.table{width:100%;margin-bottom:1rem;color:#212529}.table th,.table td{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm th,.table-sm td{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered th,.table-bordered td{border:1px solid #dee2e6}.table-bordered thead th,.table-bordered thead td{border-bottom-width:2px}.table-borderless th,.table-borderless td,.table-borderless thead th,.table-borderless tbody+tbody{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,0.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,0.075)}.table-primary,.table-primary>th,.table-primary>td{background-color:#b8e1fd}.table-primary th,.table-primary td,.table-primary thead th,.table-primary tbody+tbody{border-color:#7ac7fc}.table-hover .table-primary:hover{background-color:#9fd7fc}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fd7fc}.table-secondary,.table-secondary>th,.table-secondary>td{background-color:#e9f6fe}.table-secondary th,.table-secondary td,.table-secondary thead th,.table-secondary tbody+tbody{border-color:#d7eefe}.table-hover .table-secondary:hover{background-color:#d1ecfd}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#d1ecfd}.table-success,.table-success>th,.table-success>td{background-color:#bdf0ea}.table-success th,.table-success td,.table-success thead th,.table-success tbody+tbody{border-color:#85e3d8}.table-hover .table-success:hover{background-color:#a8ebe3}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#a8ebe3}.table-info,.table-info>th,.table-info>td{background-color:#e2d4fa}.table-info th,.table-info td,.table-info thead th,.table-info tbody+tbody{border-color:#c8aff6}.table-hover .table-info:hover{background-color:#d3bdf7}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#d3bdf7}.table-warning,.table-warning>th,.table-warning>td{background-color:#ffe6be}.table-warning th,.table-warning td,.table-warning thead th,.table-warning tbody+tbody{border-color:#ffd086}.table-hover .table-warning:hover{background-color:#ffdca5}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffdca5}.table-danger,.table-danger>th,.table-danger>td{background-color:#ffcace}.table-danger th,.table-danger td,.table-danger thead th,.table-danger tbody+tbody{border-color:#ff9ca5}.table-hover .table-danger:hover{background-color:#ffb1b6}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#ffb1b6}.table-light,.table-light>th,.table-light>td{background-color:#fdfdfe}.table-light th,.table-light td,.table-light thead th,.table-light tbody+tbody{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>th,.table-dark>td{background-color:#c6c8ca}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>th,.table-active>td{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,0.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark th,.table-dark td,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,0.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,0.075)}@media (max-width: 575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width: 767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width: 991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width: 1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#7ac8ff;outline:0;box-shadow:0 0 0 .2rem rgba(0,147,249,0.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[size],select.form-control[multiple]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*="col-"]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled ~ .form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#15CAB4}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(21,202,180,0.9);border-radius:.25rem}.was-validated .form-control:valid,.form-control.is-valid{border-color:#15CAB4;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2315CAB4' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#15CAB4;box-shadow:0 0 0 .2rem rgba(21,202,180,0.25)}.was-validated .form-control:valid ~ .valid-feedback,.was-validated .form-control:valid ~ .valid-tooltip,.form-control.is-valid ~ .valid-feedback,.form-control.is-valid ~ .valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .custom-select:valid,.custom-select.is-valid{border-color:#15CAB4;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2315CAB4' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .custom-select:valid:focus,.custom-select.is-valid:focus{border-color:#15CAB4;box-shadow:0 0 0 .2rem rgba(21,202,180,0.25)}.was-validated .custom-select:valid ~ .valid-feedback,.was-validated .custom-select:valid ~ .valid-tooltip,.custom-select.is-valid ~ .valid-feedback,.custom-select.is-valid ~ .valid-tooltip{display:block}.was-validated .form-control-file:valid ~ .valid-feedback,.was-validated .form-control-file:valid ~ .valid-tooltip,.form-control-file.is-valid ~ .valid-feedback,.form-control-file.is-valid ~ .valid-tooltip{display:block}.was-validated .form-check-input:valid ~ .form-check-label,.form-check-input.is-valid ~ .form-check-label{color:#15CAB4}.was-validated .form-check-input:valid ~ .valid-feedback,.was-validated .form-check-input:valid ~ .valid-tooltip,.form-check-input.is-valid ~ .valid-feedback,.form-check-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-control-input:valid ~ .custom-control-label,.custom-control-input.is-valid ~ .custom-control-label{color:#15CAB4}.was-validated .custom-control-input:valid ~ .custom-control-label::before,.custom-control-input.is-valid ~ .custom-control-label::before{border-color:#15CAB4}.was-validated .custom-control-input:valid ~ .valid-feedback,.was-validated .custom-control-input:valid ~ .valid-tooltip,.custom-control-input.is-valid ~ .valid-feedback,.custom-control-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before,.custom-control-input.is-valid:checked ~ .custom-control-label::before{border-color:#29e9d1;background-color:#29e9d1}.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before,.custom-control-input.is-valid:focus ~ .custom-control-label::before{box-shadow:0 0 0 .2rem rgba(21,202,180,0.25)}.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before{border-color:#15CAB4}.was-validated .custom-file-input:valid ~ .custom-file-label,.custom-file-input.is-valid ~ .custom-file-label{border-color:#15CAB4}.was-validated .custom-file-input:valid ~ .valid-feedback,.was-validated .custom-file-input:valid ~ .valid-tooltip,.custom-file-input.is-valid ~ .valid-feedback,.custom-file-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-file-input:valid:focus ~ .custom-file-label,.custom-file-input.is-valid:focus ~ .custom-file-label{border-color:#15CAB4;box-shadow:0 0 0 .2rem rgba(21,202,180,0.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#ff4051}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(255,64,81,0.9);border-radius:.25rem}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#ff4051;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff4051' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23ff4051' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#ff4051;box-shadow:0 0 0 .2rem rgba(255,64,81,0.25)}.was-validated .form-control:invalid ~ .invalid-feedback,.was-validated .form-control:invalid ~ .invalid-tooltip,.form-control.is-invalid ~ .invalid-feedback,.form-control.is-invalid ~ .invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .custom-select:invalid,.custom-select.is-invalid{border-color:#ff4051;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff4051' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23ff4051' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .custom-select:invalid:focus,.custom-select.is-invalid:focus{border-color:#ff4051;box-shadow:0 0 0 .2rem rgba(255,64,81,0.25)}.was-validated .custom-select:invalid ~ .invalid-feedback,.was-validated .custom-select:invalid ~ .invalid-tooltip,.custom-select.is-invalid ~ .invalid-feedback,.custom-select.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-control-file:invalid ~ .invalid-feedback,.was-validated .form-control-file:invalid ~ .invalid-tooltip,.form-control-file.is-invalid ~ .invalid-feedback,.form-control-file.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-check-input:invalid ~ .form-check-label,.form-check-input.is-invalid ~ .form-check-label{color:#ff4051}.was-validated .form-check-input:invalid ~ .invalid-feedback,.was-validated .form-check-input:invalid ~ .invalid-tooltip,.form-check-input.is-invalid ~ .invalid-feedback,.form-check-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-control-input:invalid ~ .custom-control-label,.custom-control-input.is-invalid ~ .custom-control-label{color:#ff4051}.was-validated .custom-control-input:invalid ~ .custom-control-label::before,.custom-control-input.is-invalid ~ .custom-control-label::before{border-color:#ff4051}.was-validated .custom-control-input:invalid ~ .invalid-feedback,.was-validated .custom-control-input:invalid ~ .invalid-tooltip,.custom-control-input.is-invalid ~ .invalid-feedback,.custom-control-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before,.custom-control-input.is-invalid:checked ~ .custom-control-label::before{border-color:#ff737f;background-color:#ff737f}.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before,.custom-control-input.is-invalid:focus ~ .custom-control-label::before{box-shadow:0 0 0 .2rem rgba(255,64,81,0.25)}.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before{border-color:#ff4051}.was-validated .custom-file-input:invalid ~ .custom-file-label,.custom-file-input.is-invalid ~ .custom-file-label{border-color:#ff4051}.was-validated .custom-file-input:invalid ~ .invalid-feedback,.was-validated .custom-file-input:invalid ~ .invalid-tooltip,.custom-file-input.is-invalid ~ .invalid-feedback,.custom-file-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-file-input:invalid:focus ~ .custom-file-label,.custom-file-input.is-invalid:focus ~ .custom-file-label{border-color:#ff4051;box-shadow:0 0 0 .2rem rgba(255,64,81,0.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width: 576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group,.form-inline .custom-select{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn:focus,.btn.focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,147,249,0.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#0093f9;border-color:#0093f9}.btn-primary:hover{color:#fff;background-color:#007cd3;border-color:#0075c6}.btn-primary:focus,.btn-primary.focus{box-shadow:0 0 0 .2rem rgba(38,163,250,0.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0093f9;border-color:#0093f9}.btn-primary:not(:disabled):not(.disabled):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0075c6;border-color:#006db9}.btn-primary:not(:disabled):not(.disabled):active:focus,.btn-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,163,250,0.5)}.btn-secondary{color:#212529;background-color:#B2DEFD;border-color:#B2DEFD}.btn-secondary:hover{color:#212529;background-color:#8dcefc;border-color:#80c9fc}.btn-secondary:focus,.btn-secondary.focus{box-shadow:0 0 0 .2rem rgba(156,194,221,0.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#212529;background-color:#B2DEFD;border-color:#B2DEFD}.btn-secondary:not(:disabled):not(.disabled):active,.btn-secondary:not(:disabled):not(.disabled).active,.show>.btn-secondary.dropdown-toggle{color:#212529;background-color:#80c9fc;border-color:#74c3fb}.btn-secondary:not(:disabled):not(.disabled):active:focus,.btn-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(156,194,221,0.5)}.btn-success{color:#fff;background-color:#15CAB4;border-color:#15CAB4}.btn-success:hover{color:#fff;background-color:#11a795;border-color:#109c8b}.btn-success:focus,.btn-success.focus{box-shadow:0 0 0 .2rem rgba(56,210,191,0.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#15CAB4;border-color:#15CAB4}.btn-success:not(:disabled):not(.disabled):active,.btn-success:not(:disabled):not(.disabled).active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#109c8b;border-color:#0f9081}.btn-success:not(:disabled):not(.disabled):active:focus,.btn-success:not(:disabled):not(.disabled).active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(56,210,191,0.5)}.btn-info{color:#fff;background-color:#9665ed;border-color:#9665ed}.btn-info:hover{color:#fff;background-color:#7f43e9;border-color:#7737e8}.btn-info:focus,.btn-info.focus{box-shadow:0 0 0 .2rem rgba(166,124,240,0.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#9665ed;border-color:#9665ed}.btn-info:not(:disabled):not(.disabled):active,.btn-info:not(:disabled):not(.disabled).active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#7737e8;border-color:#6f2ce6}.btn-info:not(:disabled):not(.disabled):active:focus,.btn-info:not(:disabled):not(.disabled).active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(166,124,240,0.5)}.btn-warning{color:#212529;background-color:#ffa516;border-color:#ffa516}.btn-warning:hover{color:#212529;background-color:#ef9300;border-color:#e28b00}.btn-warning:focus,.btn-warning.focus{box-shadow:0 0 0 .2rem rgba(222,146,25,0.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffa516;border-color:#ffa516}.btn-warning:not(:disabled):not(.disabled):active,.btn-warning:not(:disabled):not(.disabled).active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#e28b00;border-color:#d58300}.btn-warning:not(:disabled):not(.disabled):active:focus,.btn-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,146,25,0.5)}.btn-danger{color:#fff;background-color:#ff4051;border-color:#ff4051}.btn-danger:hover{color:#fff;background-color:#ff1a2e;border-color:#ff0d23}.btn-danger:focus,.btn-danger.focus{box-shadow:0 0 0 .2rem rgba(255,93,107,0.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#ff4051;border-color:#ff4051}.btn-danger:not(:disabled):not(.disabled):active,.btn-danger:not(:disabled):not(.disabled).active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#ff0d23;border-color:#ff0017}.btn-danger:not(:disabled):not(.disabled):active:focus,.btn-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,93,107,0.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light:focus,.btn-light.focus{box-shadow:0 0 0 .2rem rgba(216,217,219,0.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled):active,.btn-light:not(:disabled):not(.disabled).active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled):active:focus,.btn-light:not(:disabled):not(.disabled).active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,0.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark:focus,.btn-dark.focus{box-shadow:0 0 0 .2rem rgba(82,88,93,0.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled):active,.btn-dark:not(:disabled):not(.disabled).active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled):active:focus,.btn-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,0.5)}.btn-outline-primary{color:#0093f9;border-color:#0093f9}.btn-outline-primary:hover{color:#fff;background-color:#0093f9;border-color:#0093f9}.btn-outline-primary:focus,.btn-outline-primary.focus{box-shadow:0 0 0 .2rem rgba(0,147,249,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0093f9;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled):active,.btn-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#0093f9;border-color:#0093f9}.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,147,249,0.5)}.btn-outline-secondary{color:#B2DEFD;border-color:#B2DEFD}.btn-outline-secondary:hover{color:#212529;background-color:#B2DEFD;border-color:#B2DEFD}.btn-outline-secondary:focus,.btn-outline-secondary.focus{box-shadow:0 0 0 .2rem rgba(178,222,253,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#B2DEFD;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled):active,.btn-outline-secondary:not(:disabled):not(.disabled).active,.show>.btn-outline-secondary.dropdown-toggle{color:#212529;background-color:#B2DEFD;border-color:#B2DEFD}.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(178,222,253,0.5)}.btn-outline-success{color:#15CAB4;border-color:#15CAB4}.btn-outline-success:hover{color:#fff;background-color:#15CAB4;border-color:#15CAB4}.btn-outline-success:focus,.btn-outline-success.focus{box-shadow:0 0 0 .2rem rgba(21,202,180,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#15CAB4;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled):active,.btn-outline-success:not(:disabled):not(.disabled).active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#15CAB4;border-color:#15CAB4}.btn-outline-success:not(:disabled):not(.disabled):active:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(21,202,180,0.5)}.btn-outline-info{color:#9665ed;border-color:#9665ed}.btn-outline-info:hover{color:#fff;background-color:#9665ed;border-color:#9665ed}.btn-outline-info:focus,.btn-outline-info.focus{box-shadow:0 0 0 .2rem rgba(150,101,237,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#9665ed;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled):active,.btn-outline-info:not(:disabled):not(.disabled).active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#9665ed;border-color:#9665ed}.btn-outline-info:not(:disabled):not(.disabled):active:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(150,101,237,0.5)}.btn-outline-warning{color:#ffa516;border-color:#ffa516}.btn-outline-warning:hover{color:#212529;background-color:#ffa516;border-color:#ffa516}.btn-outline-warning:focus,.btn-outline-warning.focus{box-shadow:0 0 0 .2rem rgba(255,165,22,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffa516;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled):active,.btn-outline-warning:not(:disabled):not(.disabled).active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffa516;border-color:#ffa516}.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,165,22,0.5)}.btn-outline-danger{color:#ff4051;border-color:#ff4051}.btn-outline-danger:hover{color:#fff;background-color:#ff4051;border-color:#ff4051}.btn-outline-danger:focus,.btn-outline-danger.focus{box-shadow:0 0 0 .2rem rgba(255,64,81,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#ff4051;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled):active,.btn-outline-danger:not(:disabled):not(.disabled).active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#ff4051;border-color:#ff4051}.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,64,81,0.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:focus,.btn-outline-light.focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled):active,.btn-outline-light:not(:disabled):not(.disabled).active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled):active:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:focus,.btn-outline-dark.focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled):active,.btn-outline-dark:not(:disabled):not(.disabled).active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-link{font-weight:400;color:#0093f9;text-decoration:none}.btn-link:hover{color:#0066ad;text-decoration:underline}.btn-link:focus,.btn-link.focus{text-decoration:underline;box-shadow:none}.btn-link:disabled,.btn-link.disabled{color:#6c757d;pointer-events:none}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{transition:opacity 0.15s linear}@media (prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing{transition:none}}.dropup,.dropright,.dropdown,.dropleft{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width: 576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width: 768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width: 992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width: 1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^="top"],.dropdown-menu[x-placement^="right"],.dropdown-menu[x-placement^="bottom"],.dropdown-menu[x-placement^="left"]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0093f9}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover{z-index:1}.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type="radio"],.btn-group-toggle>.btn input[type="checkbox"],.btn-group-toggle>.btn-group>.btn input[type="radio"],.btn-group-toggle>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-control-plaintext,.input-group>.custom-select,.input-group>.custom-file{position:relative;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.form-control+.form-control,.input-group>.form-control+.custom-select,.input-group>.form-control+.custom-file,.input-group>.form-control-plaintext+.form-control,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.custom-file,.input-group>.custom-select+.form-control,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.custom-file,.input-group>.custom-file+.form-control,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.custom-file{margin-left:-1px}.input-group>.form-control:focus,.input-group>.custom-select:focus,.input-group>.custom-file .custom-file-input:focus ~ .custom-file-label{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.form-control:not(:last-child),.input-group>.custom-select:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.form-control:not(:first-child),.input-group>.custom-select:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-prepend,.input-group-append{display:flex}.input-group-prepend .btn,.input-group-append .btn{position:relative;z-index:2}.input-group-prepend .btn:focus,.input-group-append .btn:focus{z-index:3}.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.input-group-text,.input-group-append .input-group-text+.btn{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type="radio"],.input-group-text input[type="checkbox"]{margin-top:0}.input-group-lg>.form-control:not(textarea),.input-group-lg>.custom-select{height:calc(1.5em + 1rem + 2px)}.input-group-lg>.form-control,.input-group-lg>.custom-select,.input-group-lg>.input-group-prepend>.input-group-text,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-append>.btn{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.form-control:not(textarea),.input-group-sm>.custom-select{height:calc(1.5em + .5rem + 2px)}.input-group-sm>.form-control,.input-group-sm>.custom-select,.input-group-sm>.input-group-prepend>.input-group-text,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-append>.btn{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text,.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked ~ .custom-control-label::before{color:#fff;border-color:#0093f9;background-color:#0093f9}.custom-control-input:focus ~ .custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,147,249,0.25)}.custom-control-input:focus:not(:checked) ~ .custom-control-label::before{border-color:#7ac8ff}.custom-control-input:not(:disabled):active ~ .custom-control-label::before{color:#fff;background-color:#adddff;border-color:#adddff}.custom-control-input:disabled ~ .custom-control-label{color:#6c757d}.custom-control-input:disabled ~ .custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50% / 50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before{border-color:#0093f9;background-color:#0093f9}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(0,147,249,0.5)}.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before{background-color:rgba(0,147,249,0.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(0,147,249,0.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked ~ .custom-control-label::after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(0,147,249,0.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;appearance:none}.custom-select:focus{border-color:#7ac8ff;outline:0;box-shadow:0 0 0 .2rem rgba(0,147,249,0.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus ~ .custom-file-label{border-color:#7ac8ff;box-shadow:0 0 0 .2rem rgba(0,147,249,0.25)}.custom-file-input:disabled ~ .custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en) ~ .custom-file-label::after{content:"Browse"}.custom-file-input ~ .custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,147,249,0.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,147,249,0.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,147,249,0.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0093f9;border:0;border-radius:1rem;transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#adddff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0093f9;border:0;border-radius:1rem;transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#adddff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#0093f9;border:0;border-radius:1rem;transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#adddff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:hover,.nav-link:focus{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0093f9}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:hover,.navbar-toggler:focus{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width: 575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width: 767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width: 991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width: 1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,0.9)}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:rgba(0,0,0,0.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,0.5)}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(0,0,0,0.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,0.3)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .nav-link.active{color:rgba(0,0,0,0.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,0.5);border-color:rgba(0,0,0,0.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0,0,0,0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,0.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,0.9)}.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:rgba(0,0,0,0.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,0.5)}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(255,255,255,0.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,0.25)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .nav-link.active{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,0.5);border-color:rgba(255,255,255,0.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255,255,255,0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,0.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,0.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,0.03);border-top:1px solid rgba(0,0,0,0.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:flex;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width: 576px){.card-deck{flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:flex;flex:1 0 0%;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:flex;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width: 576px){.card-group{flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width: 576px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#0093f9;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0066ad;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,147,249,0.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#0093f9;border-color:#0093f9}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.badge{transition:none}}a.badge:hover,a.badge:focus{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#0093f9}a.badge-primary:hover,a.badge-primary:focus{color:#fff;background-color:#0075c6}a.badge-primary:focus,a.badge-primary.focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,147,249,0.5)}.badge-secondary{color:#212529;background-color:#B2DEFD}a.badge-secondary:hover,a.badge-secondary:focus{color:#212529;background-color:#80c9fc}a.badge-secondary:focus,a.badge-secondary.focus{outline:0;box-shadow:0 0 0 .2rem rgba(178,222,253,0.5)}.badge-success{color:#fff;background-color:#15CAB4}a.badge-success:hover,a.badge-success:focus{color:#fff;background-color:#109c8b}a.badge-success:focus,a.badge-success.focus{outline:0;box-shadow:0 0 0 .2rem rgba(21,202,180,0.5)}.badge-info{color:#fff;background-color:#9665ed}a.badge-info:hover,a.badge-info:focus{color:#fff;background-color:#7737e8}a.badge-info:focus,a.badge-info.focus{outline:0;box-shadow:0 0 0 .2rem rgba(150,101,237,0.5)}.badge-warning{color:#212529;background-color:#ffa516}a.badge-warning:hover,a.badge-warning:focus{color:#212529;background-color:#e28b00}a.badge-warning:focus,a.badge-warning.focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,165,22,0.5)}.badge-danger{color:#fff;background-color:#ff4051}a.badge-danger:hover,a.badge-danger:focus{color:#fff;background-color:#ff0d23}a.badge-danger:focus,a.badge-danger.focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,64,81,0.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:hover,a.badge-light:focus{color:#212529;background-color:#dae0e5}a.badge-light:focus,a.badge-light.focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:hover,a.badge-dark:focus{color:#fff;background-color:#1d2124}a.badge-dark:focus,a.badge-dark.focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width: 576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004c81;background-color:#cce9fe;border-color:#b8e1fd}.alert-primary hr{border-top-color:#9fd7fc}.alert-primary .alert-link{color:#002e4e}.alert-secondary{color:#5d7384;background-color:#f0f8ff;border-color:#e9f6fe}.alert-secondary hr{border-top-color:#d1ecfd}.alert-secondary .alert-link{color:#485966}.alert-success{color:#0b695e;background-color:#d0f4f0;border-color:#bdf0ea}.alert-success hr{border-top-color:#a8ebe3}.alert-success .alert-link{color:#063b35}.alert-info{color:#4e357b;background-color:#eae0fb;border-color:#e2d4fa}.alert-info hr{border-top-color:#d3bdf7}.alert-info .alert-link{color:#372657}.alert-warning{color:#85560b;background-color:#ffedd0;border-color:#ffe6be}.alert-warning hr{border-top-color:#ffdca5}.alert-warning .alert-link{color:#563807}.alert-danger{color:#85212a;background-color:#ffd9dc;border-color:#ffcace}.alert-danger hr{border-top-color:#ffb1b6}.alert-danger .alert-link{color:#5c171d}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#0093f9;transition:width 0.6s ease}@media (prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,0.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0093f9;border-color:#0093f9}.list-group-horizontal{flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media (min-width: 576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width: 768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width: 992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width: 1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004c81;background-color:#b8e1fd}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#004c81;background-color:#9fd7fc}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004c81;border-color:#004c81}.list-group-item-secondary{color:#5d7384;background-color:#e9f6fe}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#5d7384;background-color:#d1ecfd}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#5d7384;border-color:#5d7384}.list-group-item-success{color:#0b695e;background-color:#bdf0ea}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#0b695e;background-color:#a8ebe3}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0b695e;border-color:#0b695e}.list-group-item-info{color:#4e357b;background-color:#e2d4fa}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#4e357b;background-color:#d3bdf7}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#4e357b;border-color:#4e357b}.list-group-item-warning{color:#85560b;background-color:#ffe6be}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#85560b;background-color:#ffdca5}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#85560b;border-color:#85560b}.list-group-item-danger{color:#85212a;background-color:#ffcace}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#85212a;background-color:#ffb1b6}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#85212a;border-color:#85212a}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):hover,.close:not(:disabled):not(.disabled):focus{opacity:.75}button.close{padding:0;background-color:transparent;border:0;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border:1px solid rgba(0,0,0,0.1);box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,0.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform 0.3s ease-out;transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-header,.modal-dialog-scrollable .modal-footer{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;align-items:center;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width: 1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[x-placement^="top"]{padding:.4rem 0}.bs-tooltip-top .arrow,.bs-tooltip-auto[x-placement^="top"] .arrow{bottom:0}.bs-tooltip-top .arrow::before,.bs-tooltip-auto[x-placement^="top"] .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-right,.bs-tooltip-auto[x-placement^="right"]{padding:0 .4rem}.bs-tooltip-right .arrow,.bs-tooltip-auto[x-placement^="right"] .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-right .arrow::before,.bs-tooltip-auto[x-placement^="right"] .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[x-placement^="bottom"]{padding:.4rem 0}.bs-tooltip-bottom .arrow,.bs-tooltip-auto[x-placement^="bottom"] .arrow{top:0}.bs-tooltip-bottom .arrow::before,.bs-tooltip-auto[x-placement^="bottom"] .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-left,.bs-tooltip-auto[x-placement^="left"]{padding:0 .4rem}.bs-tooltip-left .arrow,.bs-tooltip-auto[x-placement^="left"] .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-left .arrow::before,.bs-tooltip-auto[x-placement^="left"] .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::before,.popover .arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top,.bs-popover-auto[x-placement^="top"]{margin-bottom:.5rem}.bs-popover-top>.arrow,.bs-popover-auto[x-placement^="top"]>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-top>.arrow::before,.bs-popover-auto[x-placement^="top"]>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,0.25)}.bs-popover-top>.arrow::after,.bs-popover-auto[x-placement^="top"]>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-right,.bs-popover-auto[x-placement^="right"]{margin-left:.5rem}.bs-popover-right>.arrow,.bs-popover-auto[x-placement^="right"]>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-right>.arrow::before,.bs-popover-auto[x-placement^="right"]>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,0.25)}.bs-popover-right>.arrow::after,.bs-popover-auto[x-placement^="right"]>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom,.bs-popover-auto[x-placement^="bottom"]{margin-top:.5rem}.bs-popover-bottom>.arrow,.bs-popover-auto[x-placement^="bottom"]>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-bottom>.arrow::before,.bs-popover-auto[x-placement^="bottom"]>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,0.25)}.bs-popover-bottom>.arrow::after,.bs-popover-auto[x-placement^="bottom"]>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[x-placement^="bottom"] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-left,.bs-popover-auto[x-placement^="left"]{margin-right:.5rem}.bs-popover-left>.arrow,.bs-popover-auto[x-placement^="left"]>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-left>.arrow::before,.bs-popover-auto[x-placement^="left"]>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,0.25)}.bs-popover-left>.arrow::after,.bs-popover-auto[x-placement^="left"]>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-left),.active.carousel-item-right{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-right),.active.carousel-item-left{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity 0.15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50% / 100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity 0.6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.bg-primary{background-color:#0093f9 !important}a.bg-primary:hover,a.bg-primary:focus,button.bg-primary:hover,button.bg-primary:focus{background-color:#0075c6 !important}.bg-secondary{background-color:#B2DEFD !important}a.bg-secondary:hover,a.bg-secondary:focus,button.bg-secondary:hover,button.bg-secondary:focus{background-color:#80c9fc !important}.bg-success{background-color:#15CAB4 !important}a.bg-success:hover,a.bg-success:focus,button.bg-success:hover,button.bg-success:focus{background-color:#109c8b !important}.bg-info{background-color:#9665ed !important}a.bg-info:hover,a.bg-info:focus,button.bg-info:hover,button.bg-info:focus{background-color:#7737e8 !important}.bg-warning{background-color:#ffa516 !important}a.bg-warning:hover,a.bg-warning:focus,button.bg-warning:hover,button.bg-warning:focus{background-color:#e28b00 !important}.bg-danger{background-color:#ff4051 !important}a.bg-danger:hover,a.bg-danger:focus,button.bg-danger:hover,button.bg-danger:focus{background-color:#ff0d23 !important}.bg-light{background-color:#f8f9fa !important}a.bg-light:hover,a.bg-light:focus,button.bg-light:hover,button.bg-light:focus{background-color:#dae0e5 !important}.bg-dark{background-color:#343a40 !important}a.bg-dark:hover,a.bg-dark:focus,button.bg-dark:hover,button.bg-dark:focus{background-color:#1d2124 !important}.bg-white{background-color:#fff !important}.bg-transparent{background-color:transparent !important}.border{border:1px solid #dee2e6 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-right{border-right:1px solid #dee2e6 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-left{border-left:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.border-primary{border-color:#0093f9 !important}.border-secondary{border-color:#B2DEFD !important}.border-success{border-color:#15CAB4 !important}.border-info{border-color:#9665ed !important}.border-warning{border-color:#ffa516 !important}.border-danger{border-color:#ff4051 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#343a40 !important}.border-white{border-color:#fff !important}.rounded-sm{border-radius:.2rem !important}.rounded{border-radius:.25rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-right{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-left{border-top-left-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-lg{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-0{border-radius:0 !important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}}@media (min-width: 768px){.d-md-none{display:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}}@media (min-width: 992px){.d-lg-none{display:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}}@media (min-width: 1200px){.d-xl-none{display:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}}@media print{.d-print-none{display:none !important}.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.85714%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-fill{flex:1 1 auto !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}@media (min-width: 576px){.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}}@media (min-width: 768px){.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}}@media (min-width: 992px){.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}}@media (min-width: 1200px){.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}}.float-left{float:left !important}.float-right{float:right !important}.float-none{float:none !important}@media (min-width: 576px){.float-sm-left{float:left !important}.float-sm-right{float:right !important}.float-sm-none{float:none !important}}@media (min-width: 768px){.float-md-left{float:left !important}.float-md-right{float:right !important}.float-md-none{float:none !important}}@media (min-width: 992px){.float-lg-left{float:left !important}.float-lg-right{float:right !important}.float-lg-none{float:none !important}}@media (min-width: 1200px){.float-xl-left{float:left !important}.float-xl-right{float:right !important}.float-xl-none{float:none !important}}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position: sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important}.shadow{box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important}.shadow-none{box-shadow:none !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.min-vw-100{min-width:100vw !important}.min-vh-100{min-height:100vh !important}.vw-100{width:100vw !important}.vh-100{height:100vh !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:.25rem !important}.mt-1,.my-1{margin-top:.25rem !important}.mr-1,.mx-1{margin-right:.25rem !important}.mb-1,.my-1{margin-bottom:.25rem !important}.ml-1,.mx-1{margin-left:.25rem !important}.m-2{margin:.5rem !important}.mt-2,.my-2{margin-top:.5rem !important}.mr-2,.mx-2{margin-right:.5rem !important}.mb-2,.my-2{margin-bottom:.5rem !important}.ml-2,.mx-2{margin-left:.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:.25rem !important}.pt-1,.py-1{padding-top:.25rem !important}.pr-1,.px-1{padding-right:.25rem !important}.pb-1,.py-1{padding-bottom:.25rem !important}.pl-1,.px-1{padding-left:.25rem !important}.p-2{padding:.5rem !important}.pt-2,.py-2{padding-top:.5rem !important}.pr-2,.px-2{padding-right:.5rem !important}.pb-2,.py-2{padding-bottom:.5rem !important}.pl-2,.px-2{padding-left:.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-n1{margin:-.25rem !important}.mt-n1,.my-n1{margin-top:-.25rem !important}.mr-n1,.mx-n1{margin-right:-.25rem !important}.mb-n1,.my-n1{margin-bottom:-.25rem !important}.ml-n1,.mx-n1{margin-left:-.25rem !important}.m-n2{margin:-.5rem !important}.mt-n2,.my-n2{margin-top:-.5rem !important}.mr-n2,.mx-n2{margin-right:-.5rem !important}.mb-n2,.my-n2{margin-bottom:-.5rem !important}.ml-n2,.mx-n2{margin-left:-.5rem !important}.m-n3{margin:-1rem !important}.mt-n3,.my-n3{margin-top:-1rem !important}.mr-n3,.mx-n3{margin-right:-1rem !important}.mb-n3,.my-n3{margin-bottom:-1rem !important}.ml-n3,.mx-n3{margin-left:-1rem !important}.m-n4{margin:-1.5rem !important}.mt-n4,.my-n4{margin-top:-1.5rem !important}.mr-n4,.mx-n4{margin-right:-1.5rem !important}.mb-n4,.my-n4{margin-bottom:-1.5rem !important}.ml-n4,.mx-n4{margin-left:-1.5rem !important}.m-n5{margin:-3rem !important}.mt-n5,.my-n5{margin-top:-3rem !important}.mr-n5,.mx-n5{margin-right:-3rem !important}.mb-n5,.my-n5{margin-bottom:-3rem !important}.ml-n5,.mx-n5{margin-left:-3rem !important}.m-auto{margin:auto !important}.mt-auto,.my-auto{margin-top:auto !important}.mr-auto,.mx-auto{margin-right:auto !important}.mb-auto,.my-auto{margin-bottom:auto !important}.ml-auto,.mx-auto{margin-left:auto !important}@media (min-width: 576px){.m-sm-0{margin:0 !important}.mt-sm-0,.my-sm-0{margin-top:0 !important}.mr-sm-0,.mx-sm-0{margin-right:0 !important}.mb-sm-0,.my-sm-0{margin-bottom:0 !important}.ml-sm-0,.mx-sm-0{margin-left:0 !important}.m-sm-1{margin:.25rem !important}.mt-sm-1,.my-sm-1{margin-top:.25rem !important}.mr-sm-1,.mx-sm-1{margin-right:.25rem !important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem !important}.ml-sm-1,.mx-sm-1{margin-left:.25rem !important}.m-sm-2{margin:.5rem !important}.mt-sm-2,.my-sm-2{margin-top:.5rem !important}.mr-sm-2,.mx-sm-2{margin-right:.5rem !important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem !important}.ml-sm-2,.mx-sm-2{margin-left:.5rem !important}.m-sm-3{margin:1rem !important}.mt-sm-3,.my-sm-3{margin-top:1rem !important}.mr-sm-3,.mx-sm-3{margin-right:1rem !important}.mb-sm-3,.my-sm-3{margin-bottom:1rem !important}.ml-sm-3,.mx-sm-3{margin-left:1rem !important}.m-sm-4{margin:1.5rem !important}.mt-sm-4,.my-sm-4{margin-top:1.5rem !important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem !important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem !important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem !important}.m-sm-5{margin:3rem !important}.mt-sm-5,.my-sm-5{margin-top:3rem !important}.mr-sm-5,.mx-sm-5{margin-right:3rem !important}.mb-sm-5,.my-sm-5{margin-bottom:3rem !important}.ml-sm-5,.mx-sm-5{margin-left:3rem !important}.p-sm-0{padding:0 !important}.pt-sm-0,.py-sm-0{padding-top:0 !important}.pr-sm-0,.px-sm-0{padding-right:0 !important}.pb-sm-0,.py-sm-0{padding-bottom:0 !important}.pl-sm-0,.px-sm-0{padding-left:0 !important}.p-sm-1{padding:.25rem !important}.pt-sm-1,.py-sm-1{padding-top:.25rem !important}.pr-sm-1,.px-sm-1{padding-right:.25rem !important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem !important}.pl-sm-1,.px-sm-1{padding-left:.25rem !important}.p-sm-2{padding:.5rem !important}.pt-sm-2,.py-sm-2{padding-top:.5rem !important}.pr-sm-2,.px-sm-2{padding-right:.5rem !important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem !important}.pl-sm-2,.px-sm-2{padding-left:.5rem !important}.p-sm-3{padding:1rem !important}.pt-sm-3,.py-sm-3{padding-top:1rem !important}.pr-sm-3,.px-sm-3{padding-right:1rem !important}.pb-sm-3,.py-sm-3{padding-bottom:1rem !important}.pl-sm-3,.px-sm-3{padding-left:1rem !important}.p-sm-4{padding:1.5rem !important}.pt-sm-4,.py-sm-4{padding-top:1.5rem !important}.pr-sm-4,.px-sm-4{padding-right:1.5rem !important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem !important}.pl-sm-4,.px-sm-4{padding-left:1.5rem !important}.p-sm-5{padding:3rem !important}.pt-sm-5,.py-sm-5{padding-top:3rem !important}.pr-sm-5,.px-sm-5{padding-right:3rem !important}.pb-sm-5,.py-sm-5{padding-bottom:3rem !important}.pl-sm-5,.px-sm-5{padding-left:3rem !important}.m-sm-n1{margin:-.25rem !important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem !important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem !important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem !important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem !important}.m-sm-n2{margin:-.5rem !important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem !important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem !important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem !important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem !important}.m-sm-n3{margin:-1rem !important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem !important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem !important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem !important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem !important}.m-sm-n4{margin:-1.5rem !important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem !important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem !important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem !important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem !important}.m-sm-n5{margin:-3rem !important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem !important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem !important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem !important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem !important}.m-sm-auto{margin:auto !important}.mt-sm-auto,.my-sm-auto{margin-top:auto !important}.mr-sm-auto,.mx-sm-auto{margin-right:auto !important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto !important}.ml-sm-auto,.mx-sm-auto{margin-left:auto !important}}@media (min-width: 768px){.m-md-0{margin:0 !important}.mt-md-0,.my-md-0{margin-top:0 !important}.mr-md-0,.mx-md-0{margin-right:0 !important}.mb-md-0,.my-md-0{margin-bottom:0 !important}.ml-md-0,.mx-md-0{margin-left:0 !important}.m-md-1{margin:.25rem !important}.mt-md-1,.my-md-1{margin-top:.25rem !important}.mr-md-1,.mx-md-1{margin-right:.25rem !important}.mb-md-1,.my-md-1{margin-bottom:.25rem !important}.ml-md-1,.mx-md-1{margin-left:.25rem !important}.m-md-2{margin:.5rem !important}.mt-md-2,.my-md-2{margin-top:.5rem !important}.mr-md-2,.mx-md-2{margin-right:.5rem !important}.mb-md-2,.my-md-2{margin-bottom:.5rem !important}.ml-md-2,.mx-md-2{margin-left:.5rem !important}.m-md-3{margin:1rem !important}.mt-md-3,.my-md-3{margin-top:1rem !important}.mr-md-3,.mx-md-3{margin-right:1rem !important}.mb-md-3,.my-md-3{margin-bottom:1rem !important}.ml-md-3,.mx-md-3{margin-left:1rem !important}.m-md-4{margin:1.5rem !important}.mt-md-4,.my-md-4{margin-top:1.5rem !important}.mr-md-4,.mx-md-4{margin-right:1.5rem !important}.mb-md-4,.my-md-4{margin-bottom:1.5rem !important}.ml-md-4,.mx-md-4{margin-left:1.5rem !important}.m-md-5{margin:3rem !important}.mt-md-5,.my-md-5{margin-top:3rem !important}.mr-md-5,.mx-md-5{margin-right:3rem !important}.mb-md-5,.my-md-5{margin-bottom:3rem !important}.ml-md-5,.mx-md-5{margin-left:3rem !important}.p-md-0{padding:0 !important}.pt-md-0,.py-md-0{padding-top:0 !important}.pr-md-0,.px-md-0{padding-right:0 !important}.pb-md-0,.py-md-0{padding-bottom:0 !important}.pl-md-0,.px-md-0{padding-left:0 !important}.p-md-1{padding:.25rem !important}.pt-md-1,.py-md-1{padding-top:.25rem !important}.pr-md-1,.px-md-1{padding-right:.25rem !important}.pb-md-1,.py-md-1{padding-bottom:.25rem !important}.pl-md-1,.px-md-1{padding-left:.25rem !important}.p-md-2{padding:.5rem !important}.pt-md-2,.py-md-2{padding-top:.5rem !important}.pr-md-2,.px-md-2{padding-right:.5rem !important}.pb-md-2,.py-md-2{padding-bottom:.5rem !important}.pl-md-2,.px-md-2{padding-left:.5rem !important}.p-md-3{padding:1rem !important}.pt-md-3,.py-md-3{padding-top:1rem !important}.pr-md-3,.px-md-3{padding-right:1rem !important}.pb-md-3,.py-md-3{padding-bottom:1rem !important}.pl-md-3,.px-md-3{padding-left:1rem !important}.p-md-4{padding:1.5rem !important}.pt-md-4,.py-md-4{padding-top:1.5rem !important}.pr-md-4,.px-md-4{padding-right:1.5rem !important}.pb-md-4,.py-md-4{padding-bottom:1.5rem !important}.pl-md-4,.px-md-4{padding-left:1.5rem !important}.p-md-5{padding:3rem !important}.pt-md-5,.py-md-5{padding-top:3rem !important}.pr-md-5,.px-md-5{padding-right:3rem !important}.pb-md-5,.py-md-5{padding-bottom:3rem !important}.pl-md-5,.px-md-5{padding-left:3rem !important}.m-md-n1{margin:-.25rem !important}.mt-md-n1,.my-md-n1{margin-top:-.25rem !important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem !important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem !important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem !important}.m-md-n2{margin:-.5rem !important}.mt-md-n2,.my-md-n2{margin-top:-.5rem !important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem !important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem !important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem !important}.m-md-n3{margin:-1rem !important}.mt-md-n3,.my-md-n3{margin-top:-1rem !important}.mr-md-n3,.mx-md-n3{margin-right:-1rem !important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem !important}.ml-md-n3,.mx-md-n3{margin-left:-1rem !important}.m-md-n4{margin:-1.5rem !important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem !important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem !important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem !important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem !important}.m-md-n5{margin:-3rem !important}.mt-md-n5,.my-md-n5{margin-top:-3rem !important}.mr-md-n5,.mx-md-n5{margin-right:-3rem !important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem !important}.ml-md-n5,.mx-md-n5{margin-left:-3rem !important}.m-md-auto{margin:auto !important}.mt-md-auto,.my-md-auto{margin-top:auto !important}.mr-md-auto,.mx-md-auto{margin-right:auto !important}.mb-md-auto,.my-md-auto{margin-bottom:auto !important}.ml-md-auto,.mx-md-auto{margin-left:auto !important}}@media (min-width: 992px){.m-lg-0{margin:0 !important}.mt-lg-0,.my-lg-0{margin-top:0 !important}.mr-lg-0,.mx-lg-0{margin-right:0 !important}.mb-lg-0,.my-lg-0{margin-bottom:0 !important}.ml-lg-0,.mx-lg-0{margin-left:0 !important}.m-lg-1{margin:.25rem !important}.mt-lg-1,.my-lg-1{margin-top:.25rem !important}.mr-lg-1,.mx-lg-1{margin-right:.25rem !important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem !important}.ml-lg-1,.mx-lg-1{margin-left:.25rem !important}.m-lg-2{margin:.5rem !important}.mt-lg-2,.my-lg-2{margin-top:.5rem !important}.mr-lg-2,.mx-lg-2{margin-right:.5rem !important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem !important}.ml-lg-2,.mx-lg-2{margin-left:.5rem !important}.m-lg-3{margin:1rem !important}.mt-lg-3,.my-lg-3{margin-top:1rem !important}.mr-lg-3,.mx-lg-3{margin-right:1rem !important}.mb-lg-3,.my-lg-3{margin-bottom:1rem !important}.ml-lg-3,.mx-lg-3{margin-left:1rem !important}.m-lg-4{margin:1.5rem !important}.mt-lg-4,.my-lg-4{margin-top:1.5rem !important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem !important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem !important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem !important}.m-lg-5{margin:3rem !important}.mt-lg-5,.my-lg-5{margin-top:3rem !important}.mr-lg-5,.mx-lg-5{margin-right:3rem !important}.mb-lg-5,.my-lg-5{margin-bottom:3rem !important}.ml-lg-5,.mx-lg-5{margin-left:3rem !important}.p-lg-0{padding:0 !important}.pt-lg-0,.py-lg-0{padding-top:0 !important}.pr-lg-0,.px-lg-0{padding-right:0 !important}.pb-lg-0,.py-lg-0{padding-bottom:0 !important}.pl-lg-0,.px-lg-0{padding-left:0 !important}.p-lg-1{padding:.25rem !important}.pt-lg-1,.py-lg-1{padding-top:.25rem !important}.pr-lg-1,.px-lg-1{padding-right:.25rem !important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem !important}.pl-lg-1,.px-lg-1{padding-left:.25rem !important}.p-lg-2{padding:.5rem !important}.pt-lg-2,.py-lg-2{padding-top:.5rem !important}.pr-lg-2,.px-lg-2{padding-right:.5rem !important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem !important}.pl-lg-2,.px-lg-2{padding-left:.5rem !important}.p-lg-3{padding:1rem !important}.pt-lg-3,.py-lg-3{padding-top:1rem !important}.pr-lg-3,.px-lg-3{padding-right:1rem !important}.pb-lg-3,.py-lg-3{padding-bottom:1rem !important}.pl-lg-3,.px-lg-3{padding-left:1rem !important}.p-lg-4{padding:1.5rem !important}.pt-lg-4,.py-lg-4{padding-top:1.5rem !important}.pr-lg-4,.px-lg-4{padding-right:1.5rem !important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem !important}.pl-lg-4,.px-lg-4{padding-left:1.5rem !important}.p-lg-5{padding:3rem !important}.pt-lg-5,.py-lg-5{padding-top:3rem !important}.pr-lg-5,.px-lg-5{padding-right:3rem !important}.pb-lg-5,.py-lg-5{padding-bottom:3rem !important}.pl-lg-5,.px-lg-5{padding-left:3rem !important}.m-lg-n1{margin:-.25rem !important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem !important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem !important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem !important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem !important}.m-lg-n2{margin:-.5rem !important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem !important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem !important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem !important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem !important}.m-lg-n3{margin:-1rem !important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem !important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem !important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem !important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem !important}.m-lg-n4{margin:-1.5rem !important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem !important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem !important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem !important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem !important}.m-lg-n5{margin:-3rem !important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem !important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem !important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem !important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem !important}.m-lg-auto{margin:auto !important}.mt-lg-auto,.my-lg-auto{margin-top:auto !important}.mr-lg-auto,.mx-lg-auto{margin-right:auto !important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto !important}.ml-lg-auto,.mx-lg-auto{margin-left:auto !important}}@media (min-width: 1200px){.m-xl-0{margin:0 !important}.mt-xl-0,.my-xl-0{margin-top:0 !important}.mr-xl-0,.mx-xl-0{margin-right:0 !important}.mb-xl-0,.my-xl-0{margin-bottom:0 !important}.ml-xl-0,.mx-xl-0{margin-left:0 !important}.m-xl-1{margin:.25rem !important}.mt-xl-1,.my-xl-1{margin-top:.25rem !important}.mr-xl-1,.mx-xl-1{margin-right:.25rem !important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem !important}.ml-xl-1,.mx-xl-1{margin-left:.25rem !important}.m-xl-2{margin:.5rem !important}.mt-xl-2,.my-xl-2{margin-top:.5rem !important}.mr-xl-2,.mx-xl-2{margin-right:.5rem !important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem !important}.ml-xl-2,.mx-xl-2{margin-left:.5rem !important}.m-xl-3{margin:1rem !important}.mt-xl-3,.my-xl-3{margin-top:1rem !important}.mr-xl-3,.mx-xl-3{margin-right:1rem !important}.mb-xl-3,.my-xl-3{margin-bottom:1rem !important}.ml-xl-3,.mx-xl-3{margin-left:1rem !important}.m-xl-4{margin:1.5rem !important}.mt-xl-4,.my-xl-4{margin-top:1.5rem !important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem !important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem !important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem !important}.m-xl-5{margin:3rem !important}.mt-xl-5,.my-xl-5{margin-top:3rem !important}.mr-xl-5,.mx-xl-5{margin-right:3rem !important}.mb-xl-5,.my-xl-5{margin-bottom:3rem !important}.ml-xl-5,.mx-xl-5{margin-left:3rem !important}.p-xl-0{padding:0 !important}.pt-xl-0,.py-xl-0{padding-top:0 !important}.pr-xl-0,.px-xl-0{padding-right:0 !important}.pb-xl-0,.py-xl-0{padding-bottom:0 !important}.pl-xl-0,.px-xl-0{padding-left:0 !important}.p-xl-1{padding:.25rem !important}.pt-xl-1,.py-xl-1{padding-top:.25rem !important}.pr-xl-1,.px-xl-1{padding-right:.25rem !important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem !important}.pl-xl-1,.px-xl-1{padding-left:.25rem !important}.p-xl-2{padding:.5rem !important}.pt-xl-2,.py-xl-2{padding-top:.5rem !important}.pr-xl-2,.px-xl-2{padding-right:.5rem !important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem !important}.pl-xl-2,.px-xl-2{padding-left:.5rem !important}.p-xl-3{padding:1rem !important}.pt-xl-3,.py-xl-3{padding-top:1rem !important}.pr-xl-3,.px-xl-3{padding-right:1rem !important}.pb-xl-3,.py-xl-3{padding-bottom:1rem !important}.pl-xl-3,.px-xl-3{padding-left:1rem !important}.p-xl-4{padding:1.5rem !important}.pt-xl-4,.py-xl-4{padding-top:1.5rem !important}.pr-xl-4,.px-xl-4{padding-right:1.5rem !important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem !important}.pl-xl-4,.px-xl-4{padding-left:1.5rem !important}.p-xl-5{padding:3rem !important}.pt-xl-5,.py-xl-5{padding-top:3rem !important}.pr-xl-5,.px-xl-5{padding-right:3rem !important}.pb-xl-5,.py-xl-5{padding-bottom:3rem !important}.pl-xl-5,.px-xl-5{padding-left:3rem !important}.m-xl-n1{margin:-.25rem !important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem !important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem !important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem !important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem !important}.m-xl-n2{margin:-.5rem !important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem !important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem !important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem !important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem !important}.m-xl-n3{margin:-1rem !important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem !important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem !important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem !important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem !important}.m-xl-n4{margin:-1.5rem !important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem !important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem !important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem !important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem !important}.m-xl-n5{margin:-3rem !important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem !important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem !important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem !important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem !important}.m-xl-auto{margin:auto !important}.mt-xl-auto,.my-xl-auto{margin-top:auto !important}.mr-xl-auto,.mx-xl-auto{margin-right:auto !important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto !important}.ml-xl-auto,.mx-xl-auto{margin-left:auto !important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace !important}.text-justify{text-align:justify !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}@media (min-width: 576px){.text-sm-left{text-align:left !important}.text-sm-right{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.text-md-left{text-align:left !important}.text-md-right{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.text-lg-left{text-align:left !important}.text-lg-right{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.text-xl-left{text-align:left !important}.text-xl-right{text-align:right !important}.text-xl-center{text-align:center !important}}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.font-weight-light{font-weight:300 !important}.font-weight-lighter{font-weight:lighter !important}.font-weight-normal{font-weight:400 !important}.font-weight-bold{font-weight:700 !important}.font-weight-bolder{font-weight:bolder !important}.font-italic{font-style:italic !important}.text-white{color:#fff !important}.text-primary{color:#0093f9 !important}a.text-primary:hover,a.text-primary:focus{color:#0066ad !important}.text-secondary{color:#B2DEFD !important}a.text-secondary:hover,a.text-secondary:focus{color:#67befb !important}.text-success{color:#15CAB4 !important}a.text-success:hover,a.text-success:focus{color:#0e8576 !important}.text-info{color:#9665ed !important}a.text-info:hover,a.text-info:focus{color:#6721e5 !important}.text-warning{color:#ffa516 !important}a.text-warning:hover,a.text-warning:focus{color:#c97b00 !important}.text-danger{color:#ff4051 !important}a.text-danger:hover,a.text-danger:focus{color:#f30016 !important}.text-light{color:#f8f9fa !important}a.text-light:hover,a.text-light:focus{color:#cbd3da !important}.text-dark{color:#343a40 !important}a.text-dark:hover,a.text-dark:focus{color:#121416 !important}.text-body{color:#212529 !important}.text-muted{color:#6c757d !important}.text-black-50{color:rgba(0,0,0,0.5) !important}.text-white-50{color:rgba(255,255,255,0.5) !important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none !important}.text-break{word-break:break-word !important;overflow-wrap:break-word !important}.text-reset{color:inherit !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media print{*,*::before,*::after{text-shadow:none !important;box-shadow:none !important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap !important}pre,blockquote{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px !important}.container{min-width:992px !important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #dee2e6 !important}.table-dark{color:inherit}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}}html{font-size:.8rem}@media (min-width: 576px){html{font-size:.9rem}}@media (min-width: 768px){html{font-size:.9rem}}@media (min-width: 992px){html{font-size:1rem}}
diff --git a/TrainManager/src/main/resources/web/zul/database.zul b/TrainManager/src/main/resources/web/zul/database.zul
new file mode 100644 (file)
index 0000000..dd1b65b
--- /dev/null
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<zk>
+    <borderlayout vflex="1" viewModel="@id('vm') @init('hu.user.trainmanager.viewmodel.DatabaseViewModel')">
+        <north>
+            <grid hflex="1" vflex="min">
+                <auxhead>
+                    <auxheader colspan="2" >
+                        <hbox align="center" pack="center">
+                            <label hflex="1" value="Schema" style="text-align:center"/>
+                            <combobox hflex="2" model="@load(vm.schemas)" selectedItem="@bind(vm.schema)"
+                                      onChange="@command('changeSchema')" placeholder="choose schema"
+                                      style="text-align:center">
+                                <template name="model">
+                                    <comboitem label="@load(each)"/>
+                                </template>
+                            </combobox>
+                        </hbox>
+                    </auxheader>
+                    <auxheader colspan="2" >
+                        <hbox align="center" pack="center">
+                            <label hflex="1" value="Table"/>
+                            <combobox hflex="2" model="@load(vm.tables)" selectedItem="@bind(vm.table)"
+                                      onChange="@command('changeTable')" placeholder="choose table"
+                                      style="text-align:center">
+                                <template name="model">
+                                    <comboitem label="@load(each)"/>
+                                </template>
+                            </combobox>
+                            <button iconSclass="z-icon-refresh" onClick="@command('refreshTables')"/>
+                        </hbox>
+                    </auxheader>
+                    <auxheader colspan="2">
+                        <hbox align="center" pack="center">
+                            <label value="Offset"/>
+                            <combobox hflex="1" model="@load(vm.offsetValues)" selectedItem="@bind(vm.offset)"
+                                      onChange="@command('changeOffset')" placeholder="select" style="text-align:center">
+                                <template name="model">
+                                    <comboitem label="@load(each)"/>
+                                </template>
+                            </combobox>
+                        </hbox>
+                    </auxheader>
+                    <auxheader colspan="2">
+                        <hbox align="center" pack="center">
+                            <label  value="Limit" style="text-align:center"/>
+                            <combobox hflex="1" model="@load(vm.limitValues)" selectedItem="@bind(vm.limit)"
+                                      onChange="@command('changeLimit')" onClick="@command('changeLimit')"
+                                      placeholder="select" style="text-align:center">
+                                <template name="model">
+                                    <comboitem label="@load(each)"/>
+                                </template>
+                            </combobox>
+                        </hbox>
+                    </auxheader>
+                    <auxheader colspan="2">
+                        <hbox align="center" pack="center">
+                            <label hflex="1" value="Records" style="text-align:center"/>
+                            <textbox hflex="1" value="@load(vm.recordNumber)" style="text-align:center"/>
+                        </hbox>
+                    </auxheader>
+                    <auxheader>
+                        <checkbox label="type" checked="@save(vm.showTypeChecked)"/>
+                        <checkbox label="split" checked="@save(vm.showSplitChecked)"/>
+                    </auxheader>
+                </auxhead>
+                <columns>
+                    <column hflex="1"/>
+                    <column hflex="3"/>
+                    <column hflex="1"/>
+                    <column hflex="3"/>
+                    <column hflex="1"/>
+                    <column hflex="1"/>
+                    <column hflex="1"/>
+                    <column hflex="1"/>
+                    <column hflex="1"/>
+                    <column hflex="1"/>
+                    <column hflex="2"/>
+                </columns>
+            </grid>
+        </north>
+        <center>
+            <listbox model="@load(vm.records)" vflex="1" style="overflow:auto;" sizedByContent="true"
+                     span="true">
+                <listhead children="@load(vm.fieldNames)" sizable="true">
+                    <template name="children" var="item">
+                        <!--                    <listheader align="center" hflex="1">-->
+                        <listheader hflex="min" align="center">
+                            <label value="@load(item)" style="white-space: normal;" multiline="true"/>
+                        </listheader>
+                    </template>
+                </listhead>
+                <template name="model" var="line">
+                    <listitem children="@bind(line)">
+                        <template name="children" var="cell">
+                            <listcell label="@load(cell)" style="text-align: center"/>
+                        </template>
+                    </listitem>
+                </template>
+                <listfoot children="@load(vm.fieldTypes)" visible="@load(vm.showTypeChecked)">
+                    <template name="children" var="item">
+                        <listfooter align="center">
+                            <label value="@load(item)" style="white-space: normal;" multiline="true"/>
+                        </listfooter>
+                    </template>
+                </listfoot>
+            </listbox>
+        </center>
+        <south>
+            <hbox hflex="1" vflex="min" align="center" pack="center" visible="@load(vm.showSplitChecked)">
+                <hbox hflex="min" align="center" pack="center">
+                    <label hflex="1" value="Fraud type field name"/>
+                    <combobox hflex="1" model="@load(vm.fraudTypeFieldNames)"
+                              selectedItem="@bind(vm.fraudTypeFieldName)"
+                              onChange="@command('refreshFraudCaseNumbers')"
+                              placeholder="select field" style="text-align:center">
+                        <template name="model">
+                            <comboitem label="@load(each)" style="text-align:center"/>
+                        </template>
+                    </combobox>
+                </hbox>
+                <panel hflex="1" border="normal">
+                    <panelchildren>
+                        <label value="Positive fraud number"/>
+                        <slider curpos="@save(vm.desiredPositiveFraudNumber)"
+                                maxpos="@load(vm.positiveCaseNumber)"/>
+                    </panelchildren>
+                </panel>
+                <!--                <hbox hflex="2" align="center" pack="center">-->
+                <!--                    <label hflex="2" value="Positive fraud number"/>-->
+                <!--                    <slider hflex="1" curpos="@save(vm.desiredPositiveFraudNumber)"/>-->
+                <!--                </hbox>-->
+                <panel hflex="1" border="normal">
+                    <panelchildren>
+                        <label value="Negative fraud number"/>
+                        <slider curpos="@save(vm.desiredNegativeFraudNumber)"
+                                maxpos="@load(vm.negativeCaseNumber)"/>
+                    </panelchildren>
+
+                </panel>
+                <!--                <hbox hflex="2" align="right" pack="center">-->
+                <!--                    <label hflex="2" value="Negative fraud number"/>-->
+                <!--                    <slider hflex="1" curpos="@save(vm.desiredNegativeFraudNumber)"/>-->
+                <!--                </hbox>-->
+                <cell hflex="1" align="center">
+                    <checkbox label="add date and time suffix" checked="@save(vm.enableAddDateTimeSuffix)"/>
+                </cell>
+<!--                <hbox hflex="1">-->
+                    <button label="SPLIT DATABASE" onClick="@command('splitDatabase')"
+                            disabled="@load(empty vm.table or empty vm.fraudTypeFieldName or
+                        vm.desiredPositiveFraudNumber eq 0 or vm.desiredNegativeFraudNumber eq 0)"/>
+<!--                </hbox>-->
+            </hbox>
+        </south>
+    </borderlayout>
+</zk>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/zul/empty.zul b/TrainManager/src/main/resources/web/zul/empty.zul
new file mode 100644 (file)
index 0000000..437f1f1
--- /dev/null
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window></window>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/zul/fit.zul b/TrainManager/src/main/resources/web/zul/fit.zul
new file mode 100644 (file)
index 0000000..8e4f902
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.1/css/font-awesome.css" rel="stylesheet"?>
+<?link rel="stylesheet" href="webjars/bootstrap/4.6.0/css/bootstrap.min.css"?>
+<?link rel="stylesheet" href="~./css/zk-admin-bootstrap-theme.css"?>
+<?link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.18.1/build/cssreset/cssreset-min.css"?>
+
+<zk>
+    <borderlayout height="100%" apply="hu.user.trainmanager.controller.TrainTaskController">
+        <center vflex="1" autoscroll="true">
+            <listbox id="progressListbox" mold="paging" pageSize="4" sizedByContent="true" span="true">
+                <auxhead>
+                    <auxheader colspan="10" align="center">
+                        <label value="TRAIN TASKS"/>
+                        <button id="trainTasksRefreshButton" iconSclass="z-icon-refresh"/>
+                    </auxheader>
+                </auxhead>
+                <listhead sizable="true">
+                    <listheader align="center">Id</listheader>
+                    <listheader tooltiptext="Train Task Name" align="center">Name</listheader>
+                    <listheader align="center">Encoding</listheader>
+                    <listheader align="center">
+                        <label value="Feature Engineering"/>
+                    </listheader>
+                    <listheader align="center">Sampling process</listheader>
+                    <listheader align="center">Fit process</listheader>
+                    <listheader align="center">Estimator persisting</listheader>
+                    <listheader align="center">Metrics persisting</listheader>
+                    <listheader align="center">State</listheader>
+                    <listheader align="center">Select</listheader>
+                </listhead>
+                <template name="model">
+                    <listitem>
+                        <listcell label="${each.id}"/>
+                        <listcell label="${each.trainTaskName}"/>
+                        <listcell label="${each.encodingState}"/>
+                        <listcell label="${each.featureEngineeringState}"/>
+                        <listcell label="${each.samplingProcessState}"/>
+                        <listcell label="${each.fitProcessState}"/>
+                        <listcell label="${each.estimatorPersistingState}"/>
+                        <listcell label="${each.metricsPersistingState}"/>
+                        <listcell label="${each.trainTaskState}"
+                                  iconSclass="${each.trainTaskState eq 'IN_PROGRESS' ? 'z-icon-spinner z-icon-spin' : 'null'}"/>
+                        <listcell>
+                            <button disabled="${each.addButtonDisabled}" iconSclass="z-icon-plus"
+                                    forward="onClick=progressListbox.onAddButtonClick"/>
+                        </listcell>
+                    </listitem>
+                </template>
+            </listbox>
+        </center>
+        <south vflex="min">
+            <grid>
+                <columns>
+                    <column hflex="3" align="center"/>
+                    <column hflex="1" align="center"/>
+                    <column hflex="3" align="center"/>
+                    <column hflex="1" align="center"/>
+                    <column hflex="1" align="center"/>
+                    <column hflex="1" align="center"/>
+                    <column hflex="3" align="center"/>
+                    <column hflex="1" align="center"/>
+                    <column hflex="2" align="center"/>
+                    <column hflex="2" align="center"/>
+                    <column hflex="2" align="center"/>
+                </columns>
+                <rows>
+                    <row>
+                        <cell>
+                            <label value="All Train Tasks"/>
+                        </cell>
+                        <cell align="center">
+                            <textbox hflex="1" id="allTrainTaskNuber" style="text-align:center"/>
+                        </cell>
+                        <cell>
+                            <label hflex="2" value="Finished Train Tasks"/>
+                        </cell>
+                        <cell align="center">
+                            <textbox hflex="1" id="finishedTrainTaskNumber" style="text-align:center"/>
+                        </cell>
+                        <cell>
+                            <label hflex="1" value="To-do"/>
+                        </cell>
+                        <cell align="center">
+                            <textbox hflex="1" id="tobeDoneTrainTaskNumber" style="text-align:center"/>
+                        </cell>
+                        <cell>
+                            <label hflex="1" value="Currently in Progress"/>
+                        </cell>
+                        <cell align="center">
+                            <textbox hflex="1" id="currentLyInProgressTrainTaskId" style="text-align:center"/>
+                        </cell>
+                        <cell>
+                            <label hflex="1" value="CPU Core"/>
+                        </cell>
+                        <cell align="center">
+                            <combobox hflex="1" id="cpuCoreComboBox" style="text-align:center"/>
+                        </cell>
+                        <cell>
+                            <button hflex="1" id="startButton" label="START" iconSclass="z-icon-check"/>
+                        </cell>
+                    </row>
+                </rows>
+            </grid>
+        </south>
+    </borderlayout>
+
+</zk>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/zul/inputTrainParameter.zul b/TrainManager/src/main/resources/web/zul/inputTrainParameter.zul
new file mode 100644 (file)
index 0000000..1486405
--- /dev/null
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<hbox viewModel="@id('vm') @init('hu.user.trainmanager.viewmodel.InputTrainParameterViewModel')"
+      validationMessages="@id('vmsgs')" sizedByContent="true">
+    <!--    <vbox hflex="1" sizedByContent="true">-->
+    <vbox hflex="1">
+        <grid hflex="3" span="true" sizedByContent="true">
+            <auxhead>
+                <auxheader colspan="2" label="Database" align="center"/>
+                <auxheader colspan="2" label="Specific properties" align="center"/>
+            </auxhead>
+            <columns>
+                <column hflex="1"/>
+                <column hflex="1"/>
+                <column hflex="1"/>
+                <column hflex="1"/>
+            </columns>
+            <rows>
+                <row>
+                    <cell>
+                        <label hflex="1" value="Schema name"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.schemas)" selectedItem="@bind(vm.schema)"
+                                  placeholder="choose schema" style="text-align:center"
+                                  onChange="@command('changeSchema')">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                    <cell>
+                        <label hflex="1" value="Timebase field name"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.eligibleTimeBaseFields)" placeholder="choose time base"
+                                  selectedItem="@bind(vm.chosenTimeBaseField)" style="text-align:center"
+                                  onChange="@command('changeTimeBaseField')">
+                            <template name="model">
+                                <comboitem label="@load(each)" style="text-align:center"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                </row>
+                <row>
+                    <cell>
+                        <label hflex="1" value="Table name"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.tables)" selectedItem="@bind(vm.table)"
+                                  placeholder="choose table" style="text-align:center"
+                                  onChange="@command('changeTable')">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                    <cell>
+                        <label value="Transaction identifier"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.columnsContainOnlyDistinctValues)"
+                                  placeholder="choose field"
+                                  selectedItem="@bind(vm.uniqueFieldName)" style="text-align:center"
+                                  onChange="@command('changeUniqueField')">
+                            <template name="model">
+                                <comboitem label="@load(each)" style="text-align:center"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                </row>
+            </rows>
+        </grid>
+        <grid>
+            <auxhead>
+                <auxheader colspan="4" label="Feature Engineering" align="center"/>
+            </auxhead>
+            <columns>
+                <column hflex="1"/>
+                <column hflex="2"/>
+                <column hflex="1"/>
+                <column hflex="2"/>
+            </columns>
+            <rows>
+                <row>
+                    <cell>
+                        <label hflex="1" value="Enable engineering"/>
+                    </cell>
+                    <cell align="center">
+                        <checkbox hflex="1"
+                                  disabled="@load(not vm.allEncodingParametersSet or not empty vm.encodingErrorMessage)"
+                                  checked="@bind(vm.enabledFeatureEngineering)"></checkbox>
+                    </cell>
+                    <cell>
+                        <label hflex="1" value="Feature field name"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" disabled="@load(!vm.enabledFeatureEngineering)" placeholder="Choose field"
+                                  model="@load(vm.fieldCandidatesForFeatureEngineering)" style="text-align:center"
+                                  selectedItem="@bind(vm.chosenFeatureField)"
+                                  onChange="@command('checkIfSelectedFeatureFieldExist')">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                </row>
+                <row visible="@load(not empty vm.featureEngineeringErrorMessage)">
+                    <cell/>
+                    <cell/>
+                    <cell>
+                        <label value="Error message"/>
+                    </cell>
+                    <cell>
+                        <textbox value="@load(vm.featureEngineeringErrorMessage)" style="text-align:center"/>
+                    </cell>
+                </row>
+                <row>
+                    <cell>
+                        <label hflex="2" value="Retrospective day"/>
+                    </cell>
+                    <cell>
+                        <hbox align="center" pack="center">
+                            <spinner hflex="min"
+                                    disabled="@load(!vm.enabledFeatureEngineering or empty vm.chosenFeatureField)"
+                                     constraint="no negative,no empty,no zero, min 1"
+                                     value="@bind(vm.retroSpectiveDay)" style="text-align:center"/>
+                            <button disabled="@load(!vm.enabledFeatureEngineering or empty vm.chosenFeatureField)"
+                                    onClick="@command('addDayToIntervals')" iconSclass="z-icon-plus-square"
+                                    tooltiptext="Add Retrospective day">
+                            </button>
+                            <button hflex="min" disabled="@load(empty vm.presentedIntervals)" iconSclass="z-icon-trash" onClick="@command('clearPresentedIntervals')"/>
+                        </hbox>
+                    </cell>
+                    <cell>
+                        <label hflex="1" value="Intervals"/>
+                    </cell>
+                    <cell>
+                        <textbox hflex="1" disabled="@load(!vm.enabledFeatureEngineering)"
+                                 placeholder="selected days" value="@load(vm.presentedIntervals)"
+                                 style="text-align:center"/>
+                    </cell>
+                </row>
+            </rows>
+        </grid>
+    </vbox>
+    <vbox align="center" hflex="1" vflex="true">
+        <grid>
+            <auxhead>
+                <auxheader label="Encoding parameters" colspan="3" align="center"/>
+            </auxhead>
+            <columns>
+                <column hflex="1"/>
+                <column hflex="1"/>
+                <column hflex="1"/>
+            </columns>
+            <rows>
+                <row>
+                    <cell>
+                        <textbox value="@load(vm.currentField)" hflex="1" readonly="true"
+                                 placeholder="field name" style="text-align:center"/>
+                    </cell>
+                    <cell>
+                        <combobox model="@load(vm.currentApplicableEncodingTypes)"
+                                  selectedItem="@bind(vm.currentEncodingType)"
+                                  hflex="1" placeholder="choose type"
+                                  style="text-align:center">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                    <cell>
+                        <hbox align="center" pack="center">
+                            <button hflex="1" disabled="@load(vm.disabledNextEncodingTypeButton)"
+                                    onClick="@command('saveCurrentFieldAndEncodingTypeAndGetNextField')"
+                                    label="Next">
+                            </button>
+                            <button hflex="1" disabled="@load(vm.disabledFinishEncodingTypeButton)"
+                                    onClick="@command('saveLastFieldAndEncodingType')" label="Finish">
+                            </button>
+                        </hbox>
+                    </cell>
+                </row>
+            </rows>
+        </grid>
+        <hbox visible="@load(not empty vm.encodingErrorMessage)" hflex="1" align="center">
+            <label value="Error message" style="text-align:center"/>
+            <textbox hflex="3" value="@load(vm.encodingErrorMessage)" style="text-align:center"/>
+            <button hflex="min" iconSclass="z-icon-repeat" onClick="@command('clearEncodingParametersAfterError')"/>
+        </hbox>
+
+
+        <grid hflex="1" sizedByContent="true" span="true">
+            <auxhead>
+                <auxheader label="Pipeline parameters" colspan="4" align="center"/>
+            </auxhead>
+            <columns>
+                <column hflex="1"/>
+                <column hflex="2"/>
+                <column hflex="1"/>
+                <column hflex="2"/>
+            </columns>
+            <rows>
+                <row>
+                    <cell>
+                        <label value="Feature Selector"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.featureSelectors)" value="@bind(vm.featureSelectorName)"
+                                  placeholder="choose selector" style="text-align:center">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                    <cell>
+                        <label value="Sampler"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.samplers)" value="@bind(vm.samplerName)" placeholder="choose sampler"
+                                  style="text-align:center">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                </row>
+                <row>
+                    <cell>
+                        <label value="Scaler"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.scalers)" value="@bind(vm.scalerName)" placeholder="choose scaler"
+                                  style="text-align:center">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                    <cell>
+                        <label value="Model"/>
+                    </cell>
+                    <cell>
+                        <combobox hflex="1" model="@load(vm.models)" value="@bind(vm.modelName)" placeholder="choose model"
+                                  style="text-align:center">
+                            <template name="model">
+                                <comboitem label="@load(each)"/>
+                            </template>
+                        </combobox>
+                    </cell>
+                </row>
+                <row>
+                    <cell>
+                        <label value="Test size (%)"/>
+                    </cell>
+                    <cell>
+                        <spinner hflex="1" step="1" constraint="no empty,min 1 max 95" value="@bind(vm.testSizePercent)"
+                                 placeholder="set test size" style="text-align:center"/>
+                    </cell>
+                    <cell>
+                        <label value="Task name" hflex="1"/>
+                    </cell>
+                    <cell>
+                        <textbox hflex="1" id="text_box" tooltiptext="Train Task name" placeholder="type name"
+                                 constraint="no empty: Please enter Train Task name"
+                                 value="@bind(vm.trainTaskName) @validator(vm.trainTaskNameValidator)"
+                                 style="text-align:center"/>
+                    </cell>
+                </row>
+            </rows>
+        </grid>
+        <grid visible="@load(not empty vmsgs[text_box])">
+            <columns>
+                <column hflex="1"/>
+                <column hflex="1"/>
+            </columns>
+            <rows>
+                <row align="center">
+                    <cell>
+                        <label hflex="1" value="Error message"/>
+                    </cell>
+                    <cell align="center">
+                        <textbox hflex="1" value="@load(vmsgs[text_box])" style="text-align:center"/>
+                    </cell>
+                </row>
+            </rows>
+
+        </grid>
+        <hbox align="center" pack="center">
+            <button disabled="@load(vm.allEncodingParametersSet eq false or vm.modelName eq null or vm.scalerName eq null or
+        vm.samplerName eq null or vm.testSizePercent lt 1 or vm.trainTaskName eq null or vm.chosenTimeBaseField eq null or
+        vm.uniqueFieldName eq null or not empty vmsgs[text_box]) or not empty vm.featureEngineeringErrorMessage)"
+                    iconSclass="z-icon-save" tooltiptext="Save Train Task" onClick="@command('saveTrainTask')"/>
+            <button iconSclass="z-icon-trash-o" tooltiptext="Clear Train Task parameters"
+                    disabled="@load(vm.schema eq null)" onClick="@command('clearAllEncodingParameters')"/>
+        </hbox>
+    </vbox>
+</hbox>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/zul/metrics.zul b/TrainManager/src/main/resources/web/zul/metrics.zul
new file mode 100644 (file)
index 0000000..b4c48d7
--- /dev/null
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<zk>
+    <tabbox height="100%" mold="accordion"
+            viewModel="@id('vm') @init('hu.user.trainmanager.viewmodel.MetricsViewModel')">
+        <tabs align="center">
+            <tab label="Show Metrics"/>
+            <tab label="Save Metrics"/>
+        </tabs>
+        <tabpanels>
+            <tabpanel>
+                <!--                <listbox height="400px" model="@load(vm.estimatorMetrics)" sizedByContent="true">-->
+                <listbox vflex="true" model="@load(vm.metricsCsvContentDto.lines)" sizedByContent="true">
+                    <auxhead>
+                        <auxheader colspan="24" align="center">
+                            <hbox align="center">
+                                <button hflex="min" iconSclass="z-icon-refresh" onClick="@command('refreshMetrics')"/>
+                                <label value="METRIC TYPES"/>
+                            </hbox>
+                        </auxheader>
+                    </auxhead>
+                    <listhead children="@load(vm.metricsCsvContentDto.headerElements)" sizable="true">
+                        <template name="children" var="item">
+                            <listheader align="center">
+                                <label value="@load(item)" style="white-space: normal;" multiline="true"/>
+                            </listheader>
+                        </template>
+                    </listhead>
+                    <template name="model" var="line">
+                        <listitem children="@bind(line)">
+                            <template name="children" var="cell">
+                                <listcell label="@load(cell)" style="text-align: center"/>
+                            </template>
+                        </listitem>
+                    </template>
+                </listbox>
+            </tabpanel>
+            <tabpanel>
+                <borderlayout vflex="1">
+                    <north>
+                        <hbox align="center" sizedByContent="true">
+                            <label value="Full Path: "/>
+                            <textbox hflex="true" value="@load(vm.fullPath)"/>
+                        </hbox>
+                    </north>
+                    <west width="30%">
+                        <listbox model="@load(vm.directoryProperties)" vflex="true">
+                            <listhead>
+                                <listheader label="Directory"/>
+                            </listhead>
+                        </listbox>
+                        <template name="model">
+                            <listitem>
+                                <listcell label="@load(each.getDisplayName())"
+                                          onClick="@command('changeDirectory',param=each.directory)"/>
+                            </listitem>
+                        </template>
+                    </west>
+                    <center>
+                        <listbox model="@load(vm.fileProperties)" mold="paging" pageSize="4" vflex="true">
+                            <listhead>
+                                <listheader hflex="4" label="File name" align="center"/>
+                                <listheader hflex="1" label="OPERATION" align="center"/>
+                            </listhead>
+                            <template name="model">
+                                <listitem>
+                                    <listcell label="@load(each.fileName)"/>
+                                    <listcell>
+                                        <hbox align="center">
+                                            <button iconSclass="z-icon-trash-o"
+                                                    onClick="@command('deleteSelectedCsvFile',param=each.file)"/>
+                                            <button iconSclass="z-icon-download"
+                                                    onClick="@command('downloadSelectedCsvFile',param=each.file)"/>
+                                        </hbox>
+                                    </listcell>
+                                </listitem>
+                            </template>
+                        </listbox>
+                    </center>
+                    <east width="20%">
+
+                        <grid width="100%" vflex="true">
+                            <columns>
+                                <column hflex="1" label="CSV FILE" align="center"/>
+                            </columns>
+                            <rows>
+                                <row>
+                                    <textbox placeholder="type Train Task file name"
+                                             constraint="no empty: Please enter Train Task csv file name"
+                                             value="@bind(vm.trainTaskCsvFileName)"
+                                    />
+                                </row>
+                                <row>
+                                    <cell align="center">
+                                        <label value="date and time suffix"/>
+                                        <checkbox disabled="@load(empty vm.trainTaskCsvFileName)"
+                                                  checked="@save(vm.enabledTrainTaskCsvDateTimeSuffix)"/>
+                                        <button disabled="@load(empty vm.trainTaskCsvFileName)"
+                                                tooltiptext="Create Train Task csv file" iconSclass="z-icon-save"
+                                                onClick="@command('createTrainTaskCsvFile')"/>
+                                    </cell>
+                                </row>
+                                <row>
+                                    <textbox placeholder="type Metrics csv file name"
+                                             constraint="no empty: Please enter Metrics csv file name"
+                                             value="@bind(vm.metricsCsvFileName)"/>
+                                </row>
+                                <row>
+                                    <cell align="center">
+                                        <!--                                        <hbox align="center" pack="center">-->
+                                        <label hflex="2" value="date and time suffix"/>
+                                        <checkbox hflex="1" disabled="@load(empty vm.metricsCsvFileName)"
+                                                  checked="@save(vm.enabledMetricsCsvDateTimeSuffix)"/>
+                                        <button disabled="@load(empty vm.metricsCsvFileName)"
+                                                tooltiptext="Create Metrics csv file" iconSclass="z-icon-save"
+                                                onClick="@command('createMetricsCsvFile')"/>
+                                        <!--                                        </hbox>-->
+
+                                    </cell>
+                                </row>
+                            </rows>
+                        </grid>
+                    </east>
+                </borderlayout>
+            </tabpanel>
+        </tabpanels>
+    </tabbox>
+</zk>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/zul/metricsSave.zul b/TrainManager/src/main/resources/web/zul/metricsSave.zul
new file mode 100644 (file)
index 0000000..bcf49ac
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
+<zk>
+    <window
+            title="SAVE METRICS"
+            id="inputCpuParameterFormId" mode="modal"
+            minimizable="false" border="normal" position="center,center" closable="true"
+            onClose="@command('cancel')"
+            action="show: slideDown;hide: slideUp" sclass="row-title">
+        <caption sclass="caption" label="SAVE METRICS to csv file"/>
+        <borderlayout height="450px">
+            <north>
+                <hbox align="center">
+                    <label value="Full Path: "/>
+                    <textbox value="@load(vm.fullPath)"/>
+                </hbox>
+            </north>
+            <west width="30%">
+                <listbox model="@load(vm.directoryProperties)" vflex="true">
+                    <listhead>
+                        <listheader label="Directory"/>
+                    </listhead>
+                </listbox>
+                <template name="model">
+                    <listitem>
+                        <listcell label="@load(each.getDisplayName())"
+                                  onClick="@command('changeDirectory',param=each.directory)"/>
+                    </listitem>
+                </template>
+            </west>
+            <center>
+                <listbox model="@load(vm.fileProperties)" mold="paging" pageSize="4" height="100%"
+                         selectedIndex="@bind(vm.selectedFileNameIndex)">
+                    <listhead>
+                        <listheader label="File name" align="center"/>
+                    </listhead>
+                    <template name="model">
+                        <listitem>
+                            <listcell label="@load(each.fileName)"/>
+                        </listitem>
+                    </template>
+                </listbox>
+            </center>
+            <east>
+                <vbox align="center">
+                    <label value="File name"/>
+                    <textbox value="@save(vm.csvFilename)" onChange="@command()"/>
+                    <button disabled="@load(vm.saveButtonDisabled)" label="SAVE"/>
+                </vbox>
+            </east>
+        </borderlayout>
+    </window>
+</zk>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/zul/presentEncodingAndFeatureEngineering.zul b/TrainManager/src/main/resources/web/zul/presentEncodingAndFeatureEngineering.zul
new file mode 100644 (file)
index 0000000..57020d2
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<zk>
+    <div viewModel="@id('vm') @init('storedEncodingAndFeatureEngineeringParameters')"/>
+    <vbox sizedByContent="true">
+        <listbox model="@init(vm.storedEncodingAndFeatureEngineeringParameters)"
+                 selectedItem="@bind(vm.selectedStoredEncodingAndFeatureEngineeringParameter)"
+                 onSelect="@command('setDisplayedEncodingAndFeatureEngineeringParameters')">
+            <auxhead>
+                <auxheader label="Stored Encoding and Feature Engineering Parameters" colspan="5" align="center"/>
+            </auxhead>
+            <listheader>
+                <listhead colspan="5" label="Stored Encoding and Feature Engineering Parameters"/>
+            </listheader>
+            <listhead>
+                <listheader hflex="1">Id</listheader>
+                <listheader hflex="4">Schema name</listheader>
+                <listheader hflex="4">Table name</listheader>
+                <listheader hflex="4">Timebase field name</listheader>
+                <listheader hflex="2">Select</listheader>
+            </listhead>
+            <template name="model">
+                <row>
+                    <textbox value="@bind(each.id)"/>
+                    <textbox value="@bind(each.schemaName)"/>
+                    <textbox value="@bind(each.tableName)"/>
+                    <textbox value="@bind(each.timeBaseFieldName)"/>
+                    <button label="Select"
+                            onClick="@command('setEncodingAndFeatureEngineeringId',id=each.id"/>
+                </row>
+            </template>
+        </listbox>
+        <hbox>
+            <grid model="vm.displayedEncodingParameters">
+                <auxhead>
+                    <auxheader label="Encoding Parameters" colspan="2" align="center"/>
+                </auxhead>
+                <template name="model">
+                    <row>
+                        <textbox value="@load(each.fieldName"/>
+                        <textbox value="@load(each.encodingType"/>
+                    </row>
+                </template>
+            </grid>
+            <grid>
+                <auxhead>
+                    <auxheader label="Feature Engineering Parameters" colspan="2" align="center"/>
+                </auxhead>
+                <columns>
+                    <column>Chosen field name</column>
+                    <column>intervals</column>
+                </columns>
+                <rows>
+                    <row>
+                        <textbox value="@load(displayedChosenFeatureField"/>
+                        <textbox value="@load(displayedRetroSpectiveIntervals)"/>
+                    </row>
+                </rows>
+            </grid>
+        </hbox>
+    </vbox>
+</zk>
\ No newline at end of file
diff --git a/TrainManager/src/main/resources/web/zul/start.zul b/TrainManager/src/main/resources/web/zul/start.zul
new file mode 100644 (file)
index 0000000..c9aaa1b
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
+<?page title="Train Manager"?>
+<zk>
+    <window border="normal"  height="100%" width="100%" style="background:#f7f7f7;"
+    title="Train Manager">
+
+    <tabbox vflex="1">
+        <tabs>
+            <tab label="Database"/>
+            <tab label="Train task"/>
+            <tab label="Fit progress"/>
+            <tab label="Metrics"/>
+        </tabs>
+        <tabpanels>
+            <tabpanel>
+                <include src="~./zul/database.zul"/>
+            </tabpanel>
+            <tabpanel>
+                <include src="~./zul/inputTrainParameter.zul"/>
+            </tabpanel>
+            <tabpanel>
+                <include src="~./zul/fit.zul"/>
+            </tabpanel>
+            <tabpanel>
+                <include src="~./zul/metrics.zul"/>
+            </tabpanel>
+        </tabpanels>
+    </tabbox>
+    </window>
+</zk>
\ No newline at end of file
diff --git a/TrainManager/src/test/java/hu/user/trainmanager/TrainManagerApplicationTests.java b/TrainManager/src/test/java/hu/user/trainmanager/TrainManagerApplicationTests.java
new file mode 100644 (file)
index 0000000..91724dd
--- /dev/null
@@ -0,0 +1,13 @@
+package hu.user.trainmanager;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class TrainManagerApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644 (file)
index 0000000..30e9793
--- /dev/null
@@ -0,0 +1,134 @@
+#version: "3.9"
+
+services:
+#  core:
+#    build:
+#      context: ./Core
+#      dockerfile: Dockerfile
+#    environment:
+#      - SPRING_PROFILES_ACTIVE=dockerized
+#      - SPRING_RABBITMQ_HOST=rabbitmq
+#    restart: always
+#    ports:
+#      - 8081:8081
+#    networks:
+#      - backend
+#    depends_on:
+#      rabbitmq:
+#        condition: service_healthy
+
+#  core-manager:
+#    build:
+#      context: ./CoreManager
+#      dockerfile: Dockerfile
+#    environment:
+#      - SPRING_PROFILES_ACTIVE=dockerized
+#      - SPRING_RABBITMQ_HOST=rabbitmq
+#    restart: always
+#    ports:
+#      - 8082:8082
+#    networks:
+#      - backend
+#    depends_on:
+#      rabbitmq:
+#        condition: service_healthy
+
+#  train-manager:
+#    build:
+#      context: ./TrainManager
+#      dockerfile: Dockerfile
+#    environment:
+#      - SPRING_PROFILES_ACTIVE=dockerized
+#      - SPRING_RABBITMQ_HOST=rabbitmq
+#    restart: always
+#    ports:
+#      - 8084:8084
+#    networks:
+#      - backend
+#    depends_on:
+#      rabbitmq:
+#        condition: service_healthy
+
+  estimator:
+#    build:
+#      context: ./Estimator
+#      dockerfile: Dockerfile
+    image: tomi70/estimator:2.0
+    restart: on-failure
+    ports:
+      - 8083:8083
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - ESTIMATOR_LOG_LEVEL=DEBUG
+      - ESTIMATOR_LOG_FILE=/log/estimator.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train:
+#    build:
+#      context: ./Train
+#      dockerfile: Dockerfile
+    image: tomi70/train:2.0
+    restart: on-failure
+    ports:
+      - 8085:8085
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - RABBITMQ_URL=rabbitmq
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - TRAIN_LOG_LEVEL=DEBUG
+      - TRAIN_LOG_FILE=/log/train.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+
+  rabbitmq:
+    container_name: rabbitmq
+    image: "rabbitmq:3-management"
+    command: rabbitmq-server
+    ports:
+      - 5672:5672
+      - 15672:15672
+        #    restart: always
+    networks:
+      - backend
+    healthcheck:
+      test: [ "CMD", "rabbitmqctl", "status" ]
+      interval: 5s
+      timeout: 20s
+      retries: 5
+
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3306:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=pwd
+
+
+networks:
+  backend:
+volumes:
+  db_data:
+
diff --git a/docker-compose_2023_03_18.yml b/docker-compose_2023_03_18.yml
new file mode 100644 (file)
index 0000000..93c6d25
--- /dev/null
@@ -0,0 +1,169 @@
+#version: "3.9"
+
+services:
+  core:
+    build:
+      context: ./Core
+      dockerfile: Dockerfile
+#    image: tomi70/core:2.0
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/common_fraud?serverTimezone=UTC",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "create",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info"
+          }'
+    restart: always
+    ports:
+      - 8081:8081
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  core-manager:
+    build:
+      context: ./CoreManager
+      dockerfile: Dockerfile
+#    image: tomi70/core_manager:1.0
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "validate",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info"
+          }'
+
+    restart: always
+    ports:
+      - 8082:8082
+    networks:
+      - backend
+    volumes:
+      - ~/log/core_manager:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train-manager:
+    build:
+      context: ./TrainManager
+      dockerfile: Dockerfile
+#    image: tomi70/train_manager:1.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "update",
+            "home.directory" : "/opt/app",
+            "evaluation.csv.directory":"/opt/app/evaluation/csv",
+            "logging.level.root" : "info"
+          }'
+    restart: on-failure
+    ports:
+      - 8084:8084
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  estimator:
+    build:
+      context: ./Estimator
+      dockerfile: Dockerfile
+#    image: tomi70/estimator:2.1
+    restart: on-failure
+    ports:
+      - 8083:8083
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    environment:
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - ESTIMATOR_LOG_LEVEL=DEBUG
+      - ESTIMATOR_LOG_FILE=/opt/app/log/Estimator.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train:
+    build:
+      context: ./Train
+      dockerfile: Dockerfile
+#    image: tomi70/train:3.1.1
+    restart: on-failure
+    ports:
+      - 8085:8085
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    environment:
+      - RABBITMQ_URL=rabbitmq
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - TRAIN_LOG_LEVEL=DEBUG
+      - TRAIN_LOG_FILE=/opt/app/log/Train.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+
+  rabbitmq:
+    container_name: rabbitmq
+    image: "rabbitmq:3-management"
+    command: rabbitmq-server
+    ports:
+      - 5672:5672
+      - 15672:15672
+    restart: always
+    networks:
+      - backend
+    healthcheck:
+      test: [ "CMD", "rabbitmqctl", "status" ]
+      interval: 5s
+      timeout: 20s
+      retries: 5
+
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3306:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=pwd
+
+
+networks:
+  backend:
+volumes:
+  db_data:
+
diff --git a/docker-compose_2023_03_19.yml b/docker-compose_2023_03_19.yml
new file mode 100644 (file)
index 0000000..2544109
--- /dev/null
@@ -0,0 +1,169 @@
+#version: "3.9"
+
+services:
+  core:
+#    build:
+#      context: ./Core
+#      dockerfile: Dockerfile
+    image: tomi70/core:2.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/common_fraud?serverTimezone=UTC",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "create",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info"
+          }'
+    restart: always
+    ports:
+      - 8081:8081
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  core-manager:
+#    build:
+#      context: ./CoreManager
+#      dockerfile: Dockerfile
+    image: tomi70/core_manager:1.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "validate",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info"
+          }'
+
+    restart: always
+    ports:
+      - 8082:8082
+    networks:
+      - backend
+    volumes:
+      - ~/log/core_manager:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train-manager:
+#    build:
+#      context: ./TrainManager
+#      dockerfile: Dockerfile
+    image: tomi70/train_manager:1.2
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "update",
+            "home.directory" : "/opt/app",
+            "evaluation.csv.directory":"/opt/app/evaluation/csv",
+            "logging.level.root" : "info"
+          }'
+    restart: on-failure
+    ports:
+      - 8084:8084
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  estimator:
+#    build:
+#      context: ./Estimator
+#      dockerfile: Dockerfile
+    image: tomi70/estimator:2.1.1
+    restart: on-failure
+    ports:
+      - 8083:8083
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    environment:
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - ESTIMATOR_LOG_LEVEL=DEBUG
+      - ESTIMATOR_LOG_FILE=/opt/app/log/Estimator.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train:
+#    build:
+#      context: ./Train
+#      dockerfile: Dockerfile
+    image: tomi70/train:3.1.2
+    restart: on-failure
+    ports:
+      - 8085:8085
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    environment:
+      - RABBITMQ_URL=rabbitmq
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - TRAIN_LOG_LEVEL=DEBUG
+      - TRAIN_LOG_FILE=/opt/app/log/Train.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+
+  rabbitmq:
+    container_name: rabbitmq
+    image: "rabbitmq:3-management"
+    command: rabbitmq-server
+    ports:
+      - 5672:5672
+      - 15672:15672
+    restart: always
+    networks:
+      - backend
+    healthcheck:
+      test: [ "CMD", "rabbitmqctl", "status" ]
+      interval: 5s
+      timeout: 20s
+      retries: 5
+
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3306:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=pwd
+
+
+networks:
+  backend:
+volumes:
+  db_data:
+
diff --git a/docker-compose_2023_03_20.yml b/docker-compose_2023_03_20.yml
new file mode 100644 (file)
index 0000000..2a64d23
--- /dev/null
@@ -0,0 +1,169 @@
+#version: "3.9"
+
+services:
+  core:
+    #    build:
+    #      context: ./Core
+    #      dockerfile: Dockerfile
+    image: tomi70/core:2.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/common_fraud?serverTimezone=UTC",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "create",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info"
+          }'
+    restart: always
+    ports:
+      - 8081:8081
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  core-manager:
+    #    build:
+    #      context: ./CoreManager
+    #      dockerfile: Dockerfile
+    image: tomi70/core_manager:1.1.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "validate",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info"
+          }'
+
+    restart: always
+    ports:
+      - 8082:8082
+    networks:
+      - backend
+    volumes:
+      - ~/log/core_manager:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train-manager:
+    #    build:
+    #      context: ./TrainManager
+    #      dockerfile: Dockerfile
+    image: tomi70/train_manager:1.2.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "update",
+            "home.directory" : "/opt/app",
+            "evaluation.csv.directory":"/opt/app/evaluation/csv",
+            "logging.level.root" : "info"
+          }'
+    restart: on-failure
+    ports:
+      - 8084:8084
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  estimator:
+    #    build:
+    #      context: ./Estimator
+    #      dockerfile: Dockerfile
+    image: tomi70/estimator:2.1.1
+    restart: on-failure
+    ports:
+      - 8083:8083
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    environment:
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - ESTIMATOR_LOG_LEVEL=DEBUG
+      - ESTIMATOR_LOG_FILE=/opt/app/log/Estimator.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train:
+    #    build:
+    #      context: ./Train
+    #      dockerfile: Dockerfile
+    image: tomi70/train:3.1.2
+    restart: on-failure
+    ports:
+      - 8085:8085
+    networks:
+      - backend
+    volumes:
+      - ~/log:/opt/app/log
+    environment:
+      - RABBITMQ_URL=rabbitmq
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - TRAIN_LOG_LEVEL=DEBUG
+      - TRAIN_LOG_FILE=/opt/app/log/Train.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+
+  rabbitmq:
+    container_name: rabbitmq
+    image: "rabbitmq:3-management"
+    command: rabbitmq-server
+    ports:
+      - 5672:5672
+      - 15672:15672
+    restart: always
+    networks:
+      - backend
+    healthcheck:
+      test: [ "CMD", "rabbitmqctl", "status" ]
+      interval: 5s
+      timeout: 20s
+      retries: 5
+
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3306:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=pwd
+
+
+networks:
+  backend:
+volumes:
+  db_data:
+
diff --git a/docker-compose_2023_04_18.yml b/docker-compose_2023_04_18.yml
new file mode 100644 (file)
index 0000000..4528f61
--- /dev/null
@@ -0,0 +1,166 @@
+#version: "3.9"
+
+services:
+  core:
+#    build:
+#      context: ./Core
+#      dockerfile: Dockerfile
+    image: tomi70/core:2.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/common_fraud?serverTimezone=UTC",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "create",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/Core.log"
+          }'
+    restart: always
+    ports:
+      - 8081:8081
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  core-manager:
+#    build:
+#      context: ./CoreManager
+#      dockerfile: Dockerfile
+    image: tomi70/core_manager:1.1.2
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "validate",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/CoreManager.log"
+          }'
+
+    restart: always
+    ports:
+      - 8082:8082
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train-manager:
+#    build:
+#      context: ./TrainManager
+#      dockerfile: Dockerfile
+    image: tomi70/train_manager:1.2.3
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "update",
+            "home.directory" : "/opt/app",
+            "evaluation.csv.directory":"/opt/app/evaluation/csv",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/TrainManager.log"
+          }'
+    restart: on-failure
+    ports:
+      - 8084:8084
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  estimator:
+#    build:
+#      context: ./Estimator
+#      dockerfile: Dockerfile
+    image: tomi70/estimator:2.1.1
+    restart: on-failure
+    ports:
+      - 8083:8083
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - ESTIMATOR_LOG_LEVEL=DEBUG
+      - ESTIMATOR_LOG_FILE=/log/estimator.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train:
+#    build:
+#      context: ./Train
+#      dockerfile: Dockerfile
+    image: tomi70/train:3.1.2
+    restart: on-failure
+    ports:
+      - 8085:8085
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - RABBITMQ_URL=rabbitmq
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - TRAIN_LOG_LEVEL=DEBUG
+      - TRAIN_LOG_FILE=/log/train.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+
+  rabbitmq:
+    container_name: rabbitmq
+    image: "rabbitmq:3-management"
+    command: rabbitmq-server
+    ports:
+      - 5672:5672
+      - 15672:15672
+      #    restart: always
+    networks:
+      - backend
+    healthcheck:
+      test: [ "CMD", "rabbitmqctl", "status" ]
+      interval: 5s
+      timeout: 20s
+      retries: 5
+
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3306:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=pwd
+
+
+networks:
+  backend:
+volumes:
+  db_data:
+
diff --git a/docker_compose_2022_02_24.yml b/docker_compose_2022_02_24.yml
new file mode 100644 (file)
index 0000000..5d3d1e8
--- /dev/null
@@ -0,0 +1,166 @@
+#version: "3.9"
+
+services:
+  core:
+#    build:
+#      context: ./Core
+#      dockerfile: Dockerfile
+    image: tomi70/core:2.0
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/common_fraud?serverTimezone=UTC",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "create",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/Core.log"
+          }'
+    restart: always
+    ports:
+      - 8081:8081
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  core-manager:
+#    build:
+#      context: ./CoreManager
+#      dockerfile: Dockerfile
+    image: tomi70/core_manager:1.0
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "validate",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/CoreManager.log"
+          }'
+
+    restart: always
+    ports:
+      - 8082:8082
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train-manager:
+#    build:
+#      context: ./TrainManager
+#      dockerfile: Dockerfile
+    image: tomi70/train_manager:1.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "update",
+            "home.directory" : "/opt/app",
+            "evaluation.csv.directory":"/opt/app/evaluation/csv",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/TrainManager.log"
+          }'
+    restart: on-failure
+    ports:
+      - 8084:8084
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  estimator:
+#    build:
+#      context: ./Estimator
+#      dockerfile: Dockerfile
+    image: tomi70/estimator:2.1
+    restart: on-failure
+    ports:
+      - 8083:8083
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - ESTIMATOR_LOG_LEVEL=DEBUG
+      - ESTIMATOR_LOG_FILE=/log/estimator.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train:
+#    build:
+#      context: ./Train
+#      dockerfile: Dockerfile
+    image: tomi70/train:3.1.1
+    restart: on-failure
+    ports:
+      - 8085:8085
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - RABBITMQ_URL=rabbitmq
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - TRAIN_LOG_LEVEL=DEBUG
+      - TRAIN_LOG_FILE=/log/train.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+
+  rabbitmq:
+    container_name: rabbitmq
+    image: "rabbitmq:3-management"
+    command: rabbitmq-server
+    ports:
+      - 5672:5672
+      - 15672:15672
+      #    restart: always
+    networks:
+      - backend
+    healthcheck:
+      test: [ "CMD", "rabbitmqctl", "status" ]
+      interval: 5s
+      timeout: 20s
+      retries: 5
+
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3306:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=pwd
+
+
+networks:
+  backend:
+volumes:
+  db_data:
+
diff --git a/mysql-docker-compose.yml b/mysql-docker-compose.yml
new file mode 100644 (file)
index 0000000..b06b573
--- /dev/null
@@ -0,0 +1,26 @@
+#version: "3.9"
+
+services:
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3307:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+      - ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
+
+    environment:
+      - MYSQL_USER=fraud
+      - MYSQL_PASSWORD=password
+      - MYSQL_ROOT_PASSWORD=pwd
+
+networks:
+  backend:
+volumes:
+  db_data:
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..386eee0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>hu.user</groupId>
+    <artifactId>CreditCardFraudDetect</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+    <modules>
+        <module>TrainManager</module>
+        <module>CoreManager</module>
+        <module>Core</module>
+        <module>CommonDto</module>
+        <module>CoreCli</module>
+        <module>TrainCli</module>
+    </modules>
+</project>
\ No newline at end of file
diff --git a/thering@mkigui b/thering@mkigui
new file mode 100644 (file)
index 0000000..4528f61
--- /dev/null
@@ -0,0 +1,166 @@
+#version: "3.9"
+
+services:
+  core:
+#    build:
+#      context: ./Core
+#      dockerfile: Dockerfile
+    image: tomi70/core:2.1
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/common_fraud?serverTimezone=UTC",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "create",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/Core.log"
+          }'
+    restart: always
+    ports:
+      - 8081:8081
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  core-manager:
+#    build:
+#      context: ./CoreManager
+#      dockerfile: Dockerfile
+    image: tomi70/core_manager:1.1.2
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "validate",
+            "home.directory" : "/opt/app",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/CoreManager.log"
+          }'
+
+    restart: always
+    ports:
+      - 8082:8082
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train-manager:
+#    build:
+#      context: ./TrainManager
+#      dockerfile: Dockerfile
+    image: tomi70/train_manager:1.2.3
+    environment:
+      - SPRING_PROFILES_ACTIVE=dockerized
+      - SPRING_RABBITMQ_HOST=rabbitmq
+      - 'SPRING_APPLICATION_JSON={
+            "spring.datasource.url"  : "jdbc:mysql://mysql_db:3306/$common_fraud?useSSL=false",
+            "spring.datasource.username" : "root",
+            "spring.datasource.password" : "pwd",
+            "spring.jpa.hibernate.ddl-auto" : "update",
+            "home.directory" : "/opt/app",
+            "evaluation.csv.directory":"/opt/app/evaluation/csv",
+            "logging.level.root" : "info",
+            "logging.file" : "/opt/app/TrainManager.log"
+          }'
+    restart: on-failure
+    ports:
+      - 8084:8084
+    networks:
+      - backend
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  estimator:
+#    build:
+#      context: ./Estimator
+#      dockerfile: Dockerfile
+    image: tomi70/estimator:2.1.1
+    restart: on-failure
+    ports:
+      - 8083:8083
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - ESTIMATOR_LOG_LEVEL=DEBUG
+      - ESTIMATOR_LOG_FILE=/log/estimator.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+  train:
+#    build:
+#      context: ./Train
+#      dockerfile: Dockerfile
+    image: tomi70/train:3.1.2
+    restart: on-failure
+    ports:
+      - 8085:8085
+    networks:
+      - backend
+    volumes:
+      - ~/log:/log
+    environment:
+      - RABBITMQ_URL=rabbitmq
+      - MYSQL_DATABASE_URL=mysql_db
+      - MYSQL_USER=root
+      - MYSQL_ROOT_PASSWORD=pwd
+      - TRAIN_LOG_LEVEL=DEBUG
+      - TRAIN_LOG_FILE=/log/train.log
+    depends_on:
+      rabbitmq:
+        condition: service_healthy
+
+
+  rabbitmq:
+    container_name: rabbitmq
+    image: "rabbitmq:3-management"
+    command: rabbitmq-server
+    ports:
+      - 5672:5672
+      - 15672:15672
+      #    restart: always
+    networks:
+      - backend
+    healthcheck:
+      test: [ "CMD", "rabbitmqctl", "status" ]
+      interval: 5s
+      timeout: 20s
+      retries: 5
+
+  mysql_db:
+    image: mysql:8.0.28
+    command: mysqld --default-authentication-plugin=mysql_native_password
+    restart: always
+    ports:
+      - 3306:3306
+    security_opt:
+      - seccomp:unconfined
+    networks:
+      - backend
+    volumes:
+      - db_data:/var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=pwd
+
+
+networks:
+  backend:
+volumes:
+  db_data:
+