commit 870cd16363c16fe304c3806959eb003af4a5a5e7
Author: Roman Kalivoda <kalivodaroman@gmail.com>
Date:   Mon May 24 15:16:16 2021 +0200

    Re #8953 Implement prediction

diff --git a/Server/ServerApp/Predictor/FeatureExtractor.cs b/Server/ServerApp/Predictor/FeatureExtractor.cs
index 103052c..1cb4936 100644
--- a/Server/ServerApp/Predictor/FeatureExtractor.cs
+++ b/Server/ServerApp/Predictor/FeatureExtractor.cs
@@ -78,9 +78,9 @@ namespace ServerApp.Predictor
                 List<int> maxima = new List<int>();
                 foreach (string building in buildings)
                 {
-
-                    amounts.Add(attendance.Where(activity => activity.building.Equals(building) && activity.startTime.Equals(input.Time)).Select(activity => activity.amount).First());
-                    maxima.Add(attendance.Where(activity => activity.building.Equals(building)).Select(activity => activity.amount).Max());
+                    List<ActivityInfo> temp = attendance.Where(activity => activity.building.Equals(building)).ToList();
+                    amounts.Add(temp.Where(activity => activity.startTime.Equals(input.Time)).Select(activity => activity.amount).First());
+                    maxima.Add(temp.Select(activity => activity.amount).Max());
                 }
                 double ratio = amounts.Sum() / (double)maxima.Sum();
                 input.Label = RatioToLabel(ratio);
@@ -133,7 +133,7 @@ namespace ServerApp.Predictor
             }
         }
 
-        private double LabelToRatio(string label)
+        internal double LabelToRatio(string label)
         {
             if (label.Equals("10%"))
             {
diff --git a/Server/ServerApp/Predictor/NaiveBayesClassifier.cs b/Server/ServerApp/Predictor/NaiveBayesClassifier.cs
index 8dbb351..7af64ad 100644
--- a/Server/ServerApp/Predictor/NaiveBayesClassifier.cs
+++ b/Server/ServerApp/Predictor/NaiveBayesClassifier.cs
@@ -43,7 +43,7 @@ namespace ServerApp.Predictor
                 .Append(_mlContext.Transforms.Conversion.ConvertType(nameof(ModelInput.Hour)))
                 .Append(_mlContext.Transforms.Concatenate("Features", 
                 new[] { nameof(ModelInput.Temp), nameof(ModelInput.Rain), nameof(ModelInput.Wind), nameof(ModelInput.Hour) }))
-                .Append(_mlContext.Transforms.NormalizeMinMax("Features", "Features"))
+                .Append(_mlContext.Transforms.NormalizeMeanVariance("Features", useCdf:false))
                 .AppendCacheCheckpoint(_mlContext)
                 .Append(_mlContext.MulticlassClassification.Trainers.NaiveBayes())
                 .Append(_mlContext.Transforms.Conversion.MapKeyToValue(nameof(ModelOutput.PredictedLabel)));
diff --git a/Server/ServerApp/Predictor/PredictionController.cs b/Server/ServerApp/Predictor/PredictionController.cs
index e100a7e..2cd8386 100644
--- a/Server/ServerApp/Predictor/PredictionController.cs
+++ b/Server/ServerApp/Predictor/PredictionController.cs
@@ -7,13 +7,15 @@ using System.Collections.Generic;
 using ServerApp.Connection.XMLProtocolHandler;
 using ServerApp.Parser.Parsers;
 using Newtonsoft.Json;
+using ServerApp.WeatherPredictionParser;
+using ServerApp.Parser.OutputInfo;
 
 namespace ServerApp.Predictor
 {
     /// <summary>
     /// Implentation of the <c>IPredicitionController</c> interface.
     /// </summary>
-    class PredictionController : IPredictionController
+    public class PredictionController : IPredictionController
     {
         /// <summary>
         /// Configuration of the <c>Predictor</c>
@@ -32,12 +34,18 @@ namespace ServerApp.Predictor
         /// </summary>
         private FeatureExtractor FeatureExtractor;
 
+        /// <summary>
+        /// A weather prediction parser service
+        /// </summary>
+        private IJsonParser weatherService;
+
         /// <summary>
         /// Instantiates new prediction controller.
         /// </summary>
         /// <param name="dataParser">A data parser used to get training data.</param>
-        public PredictionController(IDataParser dataParser, string pathToConfig = null)
+        public PredictionController(IJsonParser weatherService, IDataParser dataParser, string pathToConfig = null)
         {
+            this.weatherService = weatherService;
             // load config or get the default one
             if (pathToConfig is null)
             {
@@ -82,20 +90,90 @@ namespace ServerApp.Predictor
 
         public Response Predict(Request request)
         {
-            throw new NotImplementedException();
+            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)
+            {
+                DateTime end = new DateTime(year: request.end.year, month: request.end.month, day: request.end.day, hour: request.end.hour, minute: 0, second: 0);
+                DateTime current = start;
+                while (current < end)
+                {
+                    while (current.Hour < Date.MAX_HOUR)
+                    {
+                        var prediction = PredictSingle(request, current);
+                        predictions.Add(prediction);
+                        current = current.AddHours(this.Configuration.TimeResolution);
+                    }
+                    current = current.AddHours(23 - current.Hour + Date.MIN_HOUR);
+                }
+            } else
+            {
+                if (request.useWeather)
+                {
+                    predictions.Add(PredictSingle(request, start));
+                }
+            }
+            var response = new Response();
+            response.hoursPerSegment = Configuration.TimeResolution;
+            response.predicitons = predictions.ToArray();
+            return response;
         }
 
+        private Prediction PredictSingle(Request request, DateTime current)
+        {
+            double[] predictedValues = new double[this.Configuration.BuildingsToAreas.Count];
+            string[] predictedLabels = new string[this.Predictors.Count];
+            for (int i = 0; i < this.Predictors.Count; i++)
+            {
+                if (request.useWeather)
+                {
+                    predictedLabels[i] = this.Predictors[i].Predict(new ModelInput
+                    {
+                        Rain = (float)request.rain,
+                        Temp = (float)request.temperature,
+                        Wind = (float)request.wind,
+                        Hour = current.Hour,
+                        Time = current
+                    });
+                }
+                else
+                {
+                    List<WeatherInfo> weatherInfos = weatherService.GetPredictionForTime(from: current, to: current.AddHours(this.Configuration.TimeResolution));
+                    predictedLabels[i] = this.Predictors[i].Predict(new ModelInput
+                    {
+                        Rain = weatherInfos[0].rain,
+                        Temp = (float)weatherInfos[0].temp,
+                        Wind = (float)weatherInfos[0].wind,
+                        Hour = current.Hour,
+                        Time = current
+                    });
+                }
+            }
+            for (int i = 0; i < predictedValues.Length; i++)
+            {
+                predictedValues[i] = this.FeatureExtractor.LabelToRatio(predictedLabels[this.Configuration.BuildingsToAreas[TagInfo.buildings[i]]]) * 100;
+            }
+
+            Prediction prediction = new Prediction();
+            prediction.dateTime = new Date
+            {
+                year = current.Year,
+                month = current.Month,
+                day = current.Day,
+                hour = current.Hour
+            };
+            prediction.predictions = predictedValues;
+            return prediction;
+        }
 
         public void Train(string locationKey = null)
         {
             if (locationKey is null)
             // train all predictors
             {
-                // TODO A single predictor is used for all areas, so training is done only once now.
                 for (int i = 0; i < this.Predictors.Count; i++)
                 {
                     // train on all available data
-                    // TODO the train/test split is used just temporarily for demonstration.
                     List<ModelInput> data = FeatureExtractor.PrepareTrainingInput(i, DateTime.MinValue, DateTime.MaxValue);
                     Console.WriteLine("Training predictor with {0} samples.", data.Count);
                     this.Predictors[i].Fit(data);
diff --git a/Server/ServerApp/Program.cs b/Server/ServerApp/Program.cs
index b935858..274a52a 100644
--- a/Server/ServerApp/Program.cs
+++ b/Server/ServerApp/Program.cs
@@ -146,7 +146,7 @@ namespace ServerApp
 
 			// TODO nastavit čas
 			IDataParser p = new DataParser(dd);
-            IPredictionController predictionController = new PredictionController(p);
+            IPredictionController predictionController = new PredictionController(jsonP, p);
             predictionController.Train();
             //var results = predictionController.Predict()
 			
diff --git a/Server/ServerApp/WeatherPredictionParser/IJsonParser.cs b/Server/ServerApp/WeatherPredictionParser/IJsonParser.cs
index 3f34437..9659bb6 100644
--- a/Server/ServerApp/WeatherPredictionParser/IJsonParser.cs
+++ b/Server/ServerApp/WeatherPredictionParser/IJsonParser.cs
@@ -11,7 +11,7 @@ namespace ServerApp.WeatherPredictionParser
     /// <summary>
     /// Abstract class that every Json parser should inherit from
     /// </summary>
-    abstract class IJsonParser
+    public abstract class IJsonParser
     {
 
         /// <summary> Current weather </summary>
