commit 0d31f7e0964eb868338825d89bd3164c504bda13
Author: Roman Kalivoda <kalivoda@students.zcu.cz>
Date:   Thu May 27 18:28:54 2021 +0200

    Re #8953 tests

diff --git a/Server/Directory.Build.targets b/Server/Directory.Build.targets
new file mode 100644
index 0000000..d3d1c80
--- /dev/null
+++ b/Server/Directory.Build.targets
@@ -0,0 +1,24 @@
+<Project>
+  <Target Name="AddInternalsVisibleTo" BeforeTargets="BeforeCompile">
+    <!-- Add default suffix if there is no InternalsVisibleTo or InternalsVisibleToSuffix defined -->
+    <ItemGroup Condition="@(InternalsVisibleToSuffix->Count()) == 0 AND @(InternalsVisibleTo->Count()) == 0">
+      <InternalsVisibleToSuffix Include="Tests" />
+      <InternalsVisibleToSuffix Include="UnitTests" />
+      <InternalsVisibleToSuffix Include="FunctionalTests" />
+    </ItemGroup>
+
+    <!-- Handle InternalsVisibleTo -->
+    <ItemGroup Condition="'@(InternalsVisibleTo->Count())' &gt; 0">
+      <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
+        <_Parameter1>%(InternalsVisibleTo.Identity)</_Parameter1>
+      </AssemblyAttribute>
+    </ItemGroup>
+
+    <!-- Handle InternalsVisibleToSuffix -->
+    <ItemGroup Condition="@(InternalsVisibleToSuffix->Count()) &gt; 0">
+      <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
+        <_Parameter1>$(AssemblyName)%(InternalsVisibleToSuffix.Identity)</_Parameter1>
+      </AssemblyAttribute>
+    </ItemGroup>
+  </Target>
+</Project>
\ No newline at end of file
diff --git a/Server/ServerApp/App.config b/Server/ServerApp/App.config
new file mode 100644
index 0000000..a13c70b
--- /dev/null
+++ b/Server/ServerApp/App.config
@@ -0,0 +1,31 @@
+﻿<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <configSections>
+        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
+    </configSections>
+    <log4net>
+        <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
+            <param name="File" value="server.log"/>
+            <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+            <appendToFile value="true" />
+            <rollingStyle value="Size" />
+            <maxSizeRollBackups value="2" />
+            <maximumFileSize value="5MB" />
+            <staticLogFileName value="true" />
+            <threshold>DEBUG</threshold>
+            <layout type="log4net.Layout.PatternLayout">
+                <param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
+            </layout>
+        </appender>
+        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
+            <layout type="log4net.Layout.PatternLayout">
+                <param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
+            </layout>
+        </appender>
+        <root>
+            <level value="ALL" />
+            <appender-ref ref="LogFileAppender" />
+            <appender-ref ref="ConsoleAppender" />
+        </root>
+    </log4net>
+</configuration>
\ No newline at end of file
diff --git a/Server/ServerApp/Predictor/ModelInput.cs b/Server/ServerApp/Predictor/ModelInput.cs
index 12da9ae..45b94e1 100644
--- a/Server/ServerApp/Predictor/ModelInput.cs
+++ b/Server/ServerApp/Predictor/ModelInput.cs
@@ -47,5 +47,10 @@ namespace ServerApp.Predictor
         /// </summary>
         [ColumnName("Rain"), LoadColumn(4)]
         public float Rain { get; set; }
+
+        public override string ToString()
+        {
+            return $"ModelInput: {Time}; {Label}; {Temp}, {Hour}, {Wind}, {Rain}";
+        }
     }
 }
diff --git a/Server/ServerApp/Predictor/NaiveBayesClassifier.cs b/Server/ServerApp/Predictor/NaiveBayesClassifier.cs
index 7af64ad..fc657bc 100644
--- a/Server/ServerApp/Predictor/NaiveBayesClassifier.cs
+++ b/Server/ServerApp/Predictor/NaiveBayesClassifier.cs
@@ -5,6 +5,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
+using log4net;
 using Microsoft.ML;
 
 namespace ServerApp.Predictor
@@ -14,6 +16,8 @@ namespace ServerApp.Predictor
     /// </summary>
     class NaiveBayesClassifier : IPredictor
     {
+        private static readonly ILog _log = LogManager.GetLogger(typeof(NaiveBayesClassifier));
+
         /// <summary>
         /// Context of the ML.NET framework.
         /// </summary>
@@ -49,27 +53,16 @@ namespace ServerApp.Predictor
                 .Append(_mlContext.Transforms.Conversion.MapKeyToValue(nameof(ModelOutput.PredictedLabel)));
 
             var cvResults = _mlContext.MulticlassClassification.CrossValidate(this._trainingDataView, pipeline);
-            foreach (var result in cvResults)
-            {
-                var testMetrics = result.Metrics;
-                Console.WriteLine($"*************************************************************************************************************");
-                Console.WriteLine($"*       Metrics for Multi-class Classification model - Model #{result.Fold}    ");
-                Console.WriteLine($"*------------------------------------------------------------------------------------------------------------");
-                Console.WriteLine($"*       MicroAccuracy:    {testMetrics.MicroAccuracy:0.###}");
-                Console.WriteLine($"*       MacroAccuracy:    {testMetrics.MacroAccuracy:0.###}");
-                Console.WriteLine($"*       LogLoss:          {testMetrics.LogLoss:#.###}");
-                Console.WriteLine($"*       LogLossReduction: {testMetrics.LogLossReduction:#.###}");
-                Console.WriteLine($"*       Confusion Matrix: {testMetrics.ConfusionMatrix.GetFormattedConfusionTable()}");
-                Console.WriteLine($"*************************************************************************************************************");
-            }
+            _log.Debug("Cross-validated the trained model");
             this._trainedModel = cvResults.OrderByDescending(fold => fold.Metrics.MicroAccuracy).Select(fold => fold.Model).First();
-            Console.WriteLine($"Selected the model #{cvResults.OrderByDescending(fold => fold.Metrics.MicroAccuracy).Select(fold => fold.Fold).First()} as the best.");
+            _log.Info($"Selected the model #{cvResults.OrderByDescending(fold => fold.Metrics.MicroAccuracy).Select(fold => fold.Fold).First()} as the best.");
             this._predictionEngine = _mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(this._trainedModel);
 
         }
 
         public string Predict(ModelInput input)
         {
+            _log.Debug($"Predicting for input: {input}");
             return this._predictionEngine.Predict(input).PredictedLabel;
         }
 
diff --git a/Server/ServerApp/Predictor/PredictionController.cs b/Server/ServerApp/Predictor/PredictionController.cs
index c32fa53..84481f5 100644
--- a/Server/ServerApp/Predictor/PredictionController.cs
+++ b/Server/ServerApp/Predictor/PredictionController.cs
@@ -9,6 +9,8 @@ using ServerApp.Parser.Parsers;
 using Newtonsoft.Json;
 using ServerApp.WeatherPredictionParser;
 using ServerApp.Parser.OutputInfo;
+using System.Reflection;
+using log4net;
 
 namespace ServerApp.Predictor
 {
@@ -17,6 +19,8 @@ namespace ServerApp.Predictor
     /// </summary>
     public class PredictionController : IPredictionController
     {
+        private static readonly ILog _log = LogManager.GetLogger(typeof(PredictionController));
+
         /// <summary>
         /// Configuration of the <c>Predictor</c>
         /// </summary>
@@ -45,6 +49,7 @@ namespace ServerApp.Predictor
         /// <param name="dataParser">A data parser used to get training data.</param>
         public PredictionController(IJsonParser weatherService, IDataParser dataParser, string pathToConfig = null)
         {
+            _log.Info("Constructing a new PredictionController instance.");
             this.weatherService = weatherService;
             // load config or get the default one
             if (pathToConfig is null)
@@ -55,7 +60,8 @@ namespace ServerApp.Predictor
             {
                 string json = System.IO.File.ReadAllText(pathToConfig);
                 this.Configuration = JsonConvert.DeserializeObject<PredictorConfiguration>(json);
-            } catch (System.IO.IOException e)
+            }
+            catch (System.IO.IOException e)
             {
                 Console.WriteLine("Warning: could not find a configuration file, creating a new one:");
                 Console.WriteLine(e.Message.PadLeft(4));
@@ -91,6 +97,7 @@ namespace ServerApp.Predictor
 
         public Response Predict(Request request)
         {
+            _log.Info($"Received a prediction request: endDate={request.useEndDate}, weather={request.useWeather}");
             DateTime start = new DateTime(year: request.start.year, month: request.start.month, day: request.start.day, hour: request.start.hour, minute: 0, second: 0);
             List<Prediction> predictions = new List<Prediction>();
             if (request.useEndDate)
@@ -99,24 +106,26 @@ namespace ServerApp.Predictor
                 DateTime current = start;
                 while (current < end)
                 {
+                    _log.Debug($"Predicting for date {current.Date.ToShortDateString()}");
                     while (current.Hour < Date.MAX_HOUR)
                     {
+                        _log.Debug($"Predicting for time {current.TimeOfDay.ToString()}");
                         var prediction = PredictSingle(request, current);
                         predictions.Add(prediction);
                         current = current.AddHours(this.Configuration.TimeResolution);
                     }
                     current = current.AddHours(23 - current.Hour + Date.MIN_HOUR);
                 }
-            } else
+            }
+            else
             {
-                if (request.useWeather)
-                {
-                    predictions.Add(PredictSingle(request, start));
-                }
+                _log.Debug("Predicting for single DateTime.");
+                predictions.Add(PredictSingle(request, start));
             }
             var response = new Response();
             response.hoursPerSegment = Configuration.TimeResolution;
             response.predicitons = predictions.ToArray();
+            _log.Debug($"Created a response.");
             return response;
         }
 
@@ -128,6 +137,7 @@ namespace ServerApp.Predictor
             {
                 if (request.useWeather)
                 {
+                    _log.Debug("Predicting for requested weather.");
                     predictedLabels[i] = this.Predictors[i].Predict(new ModelInput
                     {
                         Rain = (float)request.rain,
@@ -139,12 +149,14 @@ namespace ServerApp.Predictor
                 }
                 else
                 {
-                    List<WeatherInfo> weatherInfos = weatherService.GetPredictionForTime(from: current, to: current.AddHours(this.Configuration.TimeResolution));
+                    _log.Debug("Retrieving weather info from the weather service.");
+                    weatherService.ParsePrediction();
+                    WeatherInfo weatherInfo = weatherService.Current;
                     predictedLabels[i] = this.Predictors[i].Predict(new ModelInput
                     {
-                        Rain = weatherInfos[0].rain,
-                        Temp = (float)weatherInfos[0].temp,
-                        Wind = (float)weatherInfos[0].wind,
+                        Rain = weatherInfo.rain,
+                        Temp = (float)weatherInfo.temp,
+                        Wind = (float)weatherInfo.wind,
                         Hour = current.Hour,
                         Time = current
                     });
@@ -164,6 +176,7 @@ namespace ServerApp.Predictor
                 hour = current.Hour
             };
             prediction.predictions = predictedValues;
+            _log.Debug($"Created prediction for DateTime: {prediction.dateTime}");
             return prediction;
         }
 
diff --git a/Server/ServerApp/Program.cs b/Server/ServerApp/Program.cs
index e155880..151456f 100644
--- a/Server/ServerApp/Program.cs
+++ b/Server/ServerApp/Program.cs
@@ -1,4 +1,5 @@
-﻿using ServerApp.Connection;
+﻿using log4net.Config;
+using ServerApp.Connection;
 using ServerApp.Connection.XMLProtocolHandler;
 using ServerApp.DataDownload;
 using ServerApp.Parser.InputData;
@@ -33,9 +34,9 @@ namespace ServerApp
 
         static void Main(string[] args)
         {
-
-			// SETUP FOLDERS
-			Config config = FillConfigInfo(args);
+            XmlConfigurator.Configure();
+            // SETUP FOLDERS
+            Config config = FillConfigInfo(args);
 			if (config == null)
 			{
 				Console.ReadLine();
@@ -180,7 +181,7 @@ namespace ServerApp
 			// TODO nastavit čas
 			IDataParser p = new DataParser(dd);
             // FIXME pass the right references to the JsonParser constructor
-            IJsonParser jsonP = new JsonParser(dd, null);
+            IJsonParser jsonP = new JsonParser(dd, new CsvDataLoader());
             IPredictionController predictionController = new PredictionController(jsonP, p);
             predictionController.Train();
             //var results = predictionController.Predict()
diff --git a/Server/ServerApp/Properties/launchSettings.json b/Server/ServerApp/Properties/launchSettings.json
index 0ee2ad8..c5a1967 100644
--- a/Server/ServerApp/Properties/launchSettings.json
+++ b/Server/ServerApp/Properties/launchSettings.json
@@ -2,7 +2,7 @@
   "profiles": {
     "ServerApp": {
       "commandName": "Project",
-      "commandLineArgs": "C:\\Users\\kalivoda\\Temp\\serverAppNetCore\\ServerAppNETCore\\ServerApp\\server_config"
+      "commandLineArgs": "C:\\Users\\kalivoda\\University\\KIV-ASWI\\aswi2021tri-musketyri\\Server\\ServerApp\\server_config"
     }
   }
 }
\ No newline at end of file
diff --git a/Server/ServerApp/ServerApp.csproj b/Server/ServerApp/ServerApp.csproj
index c7a7e81..baebe45 100644
--- a/Server/ServerApp/ServerApp.csproj
+++ b/Server/ServerApp/ServerApp.csproj
@@ -8,6 +8,7 @@
     </PropertyGroup>
 
     <ItemGroup>
+        <PackageReference Include="log4net" Version="2.0.12" />
         <PackageReference Include="Microsoft.ML" Version="1.5.5" />
     </ItemGroup>
 
@@ -26,11 +27,11 @@
         </EmbeddedResource>
     </ItemGroup>
 
-    <ItemGroup>
+    <!--<ItemGroup>
         <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
             <_Parameter1>ServerAppUnitTests</_Parameter1>
-            <!-- We use the value of AssemblyName to declare the value of the attribute -->
+            --><!-- We use the value of AssemblyName to declare the value of the attribute --><!--
         </AssemblyAttribute>
-    </ItemGroup>
+    </ItemGroup>-->
 
 </Project>
diff --git a/Server/ServerAppFunctionalTests/Predictor/PredictionControllerTests.cs b/Server/ServerAppFunctionalTests/Predictor/PredictionControllerTests.cs
new file mode 100644
index 0000000..3ba2167
--- /dev/null
+++ b/Server/ServerAppFunctionalTests/Predictor/PredictionControllerTests.cs
@@ -0,0 +1,52 @@
+﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
+using ServerApp.Predictor;
+//
+// Author: Roman Kalivoda
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ServerApp.WeatherPredictionParser;
+using ServerApp.Parser.Parsers;
+using ServerApp.Parser.InputData;
+using ServerApp.Connection.XMLProtocolHandler;
+using ServerApp.DataDownload;
+
+namespace ServerApp.Predictor.Tests
+{
+    [TestClass()]
+    public class PredictionControllerTests
+    {
+        [TestMethod()]
+        public void PredictSingleTimeWeatherTest()
+        {
+            System.IO.Directory.CreateDirectory(@".\data");
+            DataDownloader dd = new DataDownloader(@".\data\dd\", "http://openstore.zcu.cz/", "OD_ZCU_{type}_{month}_{year}_{format}.zip");
+            PredictionController instance = new PredictionController(new JsonParser(dd, new CsvDataLoader()), new DataParser(dd));
+
+            dd.DownloadData(DataType.JIS, DataFormat.CSV, new DataDownload.Date(1, 2019), new DataDownload.Date(12, 2020));
+            dd.DownloadData(DataType.STROJE, DataFormat.CSV, new DataDownload.Date(1, 2017), new DataDownload.Date(12, 2020));
+            dd.DownloadData(DataType.POCASI, DataFormat.CSV, new DataDownload.Date(1, 2017), new DataDownload.Date(12, 2020));
+            instance.Train();
+
+            Request request = new Request()
+            {
+                useEndDate = false,
+                useWeather = false,
+                start = new Connection.XMLProtocolHandler.Date()
+                {
+                    day = DateTime.Now.Day,
+                    month = DateTime.Now.Month,
+                    year = DateTime.Now.Year,
+                    hour = 10
+                }
+            };
+            Response actual = instance.Predict(request);
+            CollectionAssert.AllItemsAreInstancesOfType(actual.predicitons, typeof(Prediction));
+            Assert.AreEqual(1, actual.predicitons.Length);
+        }
+    }
+}
diff --git a/Server/ServerAppUnitTests/Predictor/NaiveBayesClassifierTests.cs b/Server/ServerAppUnitTests/Predictor/NaiveBayesClassifierTests.cs
new file mode 100644
index 0000000..cbe628c
--- /dev/null
+++ b/Server/ServerAppUnitTests/Predictor/NaiveBayesClassifierTests.cs
@@ -0,0 +1,91 @@
+﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
+using ServerApp.Predictor;
+//
+// Author: Roman Kalivoda
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Text.RegularExpressions;
+
+namespace ServerApp.Predictor.Tests
+{
+    [TestClass()]
+    public class NaiveBayesClassifierTests
+    {
+        static IPredictor predictor;
+
+        static readonly List<ModelInput> trainInput = new()
+        {
+                new ModelInput
+                {
+                    Label = "10%",
+                    Temp = 12,
+                    Hour = 7,
+                    Rain = 10,
+                    Time = DateTime.Now,
+                    Wind = 10
+                },
+                new ModelInput
+                {
+                    Label = "100%",
+                    Temp = 32,
+                    Hour = 7,
+                    Rain = 0,
+                    Time = DateTime.Now,
+                    Wind = 70
+                },
+                new ModelInput
+                {
+                    Label = "90%",
+                    Temp = 32,
+                    Hour = 15,
+                    Rain = 20,
+                    Time = DateTime.Now,
+                    Wind = 50
+                },
+                new ModelInput
+                {
+                    Label = "70%",
+                    Temp = 32,
+                    Hour = 18,
+                    Rain = 0,
+                    Time = DateTime.Now,
+                    Wind = 70
+                },
+                new ModelInput
+                {
+                    Label = "30%",
+                    Temp = 6,
+                    Hour = 10,
+                    Rain = 70,
+                    Time = DateTime.Now,
+                    Wind = 30
+                }
+            };
+
+        [ClassInitialize()]
+        public static void SetUpClass(TestContext context)
+        {
+            predictor = new NaiveBayesClassifier();
+            predictor.Fit(trainInput);
+        }
+
+        [TestMethod()]
+        public void PredictTest()
+        {
+            string actual = predictor.Predict(new ModelInput
+            {
+                Temp = 19,
+                Hour = 17,
+                Rain = 20,
+                Time = DateTime.Now,
+                Wind = 7
+            });
+            StringAssert.Matches(actual, new Regex(@"\d0%"));
+        }
+    }
+}
diff --git a/Server/ServerAppUnitTests/Predictor/PredictionControllerTests.cs b/Server/ServerAppUnitTests/Predictor/PredictionControllerTests.cs
deleted file mode 100644
index 113597c..0000000
--- a/Server/ServerAppUnitTests/Predictor/PredictionControllerTests.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
-using ServerApp.Predictor;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ServerApp.Predictor.Tests
-{
-    [TestClass()]
-    public class PredictionControllerTests
-    {
-        [TestMethod()]
-        public void PredictionControllerTest()
-        {
-            throw new NotImplementedException();
-        }
-
-        [TestMethod()]
-        public void GetPredictorsTest()
-        {
-            throw new NotImplementedException();
-        }
-
-        [TestMethod()]
-        public void LoadTest()
-        {
-            throw new NotImplementedException();
-        }
-
-        [TestMethod()]
-        public void PredictTest()
-        {
-            throw new NotImplementedException();
-        }
-
-        [TestMethod()]
-        public void TrainTest()
-        {
-            throw new NotImplementedException();
-        }
-    }
-}
