From 0e4e507733c59f667958d5cba100d94e4be09b10 Mon Sep 17 00:00:00 2001
From: Eilert Tunheim <emtunhei@stud.ntnu.no>
Date: Mon, 25 Apr 2022 10:47:39 +0200
Subject: [PATCH] Added SQL for manual measurements in getZeroPoint function,
 need to move to kwh function

---
 .../java/com/application/DB/Constants.java    |   4 +
 src/main/java/com/application/DB/DB.java      | 259 +++++-------------
 .../GUI/LineChartFunctionality.java           |  14 +-
 .../GUI/LineChartFunctionality.class          | Bin 9781 -> 9257 bytes
 4 files changed, 75 insertions(+), 202 deletions(-)

diff --git a/src/main/java/com/application/DB/Constants.java b/src/main/java/com/application/DB/Constants.java
index 8ca102c..ff18115 100644
--- a/src/main/java/com/application/DB/Constants.java
+++ b/src/main/java/com/application/DB/Constants.java
@@ -85,6 +85,10 @@ public final class Constants {
     //public static final int ARJANG_VALUE_ID = 19;
     public static final ArrayList<Integer> ARJANG_KWH_CHAMBER_IDS = new ArrayList<>(Arrays.asList(19,19));
 
+    // Moisture Manual measurements
+    public static final String ARJANG_MANFUKTREG = "int_ds_manfuktreg";
+    public static final int ARJANG_NR_ADDED_DAYS = 2;
+
 
 
 
diff --git a/src/main/java/com/application/DB/DB.java b/src/main/java/com/application/DB/DB.java
index 19a5f0b..9a72927 100644
--- a/src/main/java/com/application/DB/DB.java
+++ b/src/main/java/com/application/DB/DB.java
@@ -1,13 +1,8 @@
 package com.application.DB;
 
-import com.google.auth.oauth2.GoogleCredentials;
-import com.google.auth.oauth2.ServiceAccountCredentials;
 import com.google.cloud.bigquery.*;
 import org.joda.time.DateTime;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.text.SimpleDateFormat;
 import java.util.*;
 
 import static com.application.DB.Constants.*;
@@ -20,37 +15,6 @@ import static com.application.DB.Constants.*;
  */
 public class DB {
 
-    /**
-     * Creates a simple date format to use for converting millis in numbers to a usefull date format
-     *
-     * @return returns the date format
-     */
-    private static SimpleDateFormat getDateFormat() {
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-        return dateFormat;
-    }
-
-    /**
-     * Retrieves the credentials file
-     *
-     * @return the credentials
-     * @throws Exception for potential errors
-     */
-    private static GoogleCredentials getCredentials() throws Exception {
-        File credentialsPath = new File("./src/main/resources/com.application/sf-drying-optimization-1e234ad2b0f4.json");
-
-        // Load credentials from JSON key file. If you can't set the GOOGLE_APPLICATION_CREDENTIALS
-        // environment variable, you can explicitly load the credentials file to construct the
-        // credentials.
-        GoogleCredentials credentials;
-        try (FileInputStream serviceAccountStream = new FileInputStream(credentialsPath)) {
-            credentials = ServiceAccountCredentials.fromStream(serviceAccountStream);
-        }
-        return credentials;
-    }
-
-
     /**
      * Launching the functions
      *
@@ -66,69 +30,6 @@ public class DB {
     }
 
 
-    /**
-     * Creates a bigquery builder
-     *
-     * @return a builder
-     * @throws Exception returns potential error
-     */
-    private static BigQuery getBuilder() throws Exception {
-        // Step 1: Initialize BigQuery service
-        // Here we set our project ID and get the `BigQuery` service object
-        // this is the interface to our BigQuery instance that
-        // we use to execute jobs on
-        return BigQueryOptions.newBuilder().
-                setCredentials(getCredentials()).
-                setProjectId(PROJECT_ID)
-                .build().getService();
-    }
-
-
-    /**
-     * Creates a job
-     *
-     * @param queryConfig query configuration information
-     * @return a job
-     * @throws Exception returns potential error
-     */
-    private static Job getJob(JobConfiguration queryConfig) throws Exception {
-        // Step 3: Run the job on BigQuery
-        // create a `Job` instance from the job configuration using the BigQuery service
-        // the job starts executing once the `create` method executes
-
-        Job queryJob = getBuilder().create(JobInfo.newBuilder(queryConfig).build());
-        queryJob = queryJob.waitFor();
-        // the waitFor method blocks until the job completes
-        // and returns `null` if the job doesn't exist anymore
-        if (queryJob == null) {
-            throw new Exception("job no longer exists");
-        }
-        // once the job is done, check if any error occured
-        if (queryJob.getStatus().getError() != null) {
-            throw new Exception(queryJob.getStatus().getError().toString());
-        }
-        return queryJob;
-    }
-
-    /**
-     * This function creates a query job that uses the query statement
-     * in order to retrieve information from the database
-     *
-     * @param sqlStatement input for the query statement
-     * @return returns the queryjob with the results
-     * @throws Exception Throws exception in case of error
-     */
-    private static TableResult createQueryJob(String sqlStatement) throws Exception {
-
-        // Creates a job configuration
-        Job queryJob = getJob(QueryJobConfiguration.newBuilder(sqlStatement).build());
-
-        // Retrieves the results from the queryjob
-        return queryJob.getQueryResults();
-
-    }
-
-
     public static Map<Integer, Map<String, Number>> setInputParameters() throws Exception {
 
         Map<Integer, Map<String, Number>> allDryingPeriods = new HashMap<>();
@@ -320,51 +221,6 @@ public class DB {
     }
 
 
-
-    /**
-     *
-     *
-     * @throws Exception
-     */
-    /*
-    public static void getManualMeasurements() throws Exception {
-
-        // Sqlstatement
-        final String sqlStatement = "";
-
-        System.out.println(sqlStatement);
-
-        // Retrieves the results from the queryjob
-        TableResult result = createQueryJob(sqlStatement);
-
-        //System.out.println("InTidTork\t\t\tUtTidTork");
-        // Iterating through the results
-        for (FieldValueList row : result.iterateAll()) {
-
-            row.get("DryingStarted").getTimestampValue();
-        }
-
-        // Defining a treemap to sort the data incrementally
-        //NavigableMap<String, String> sortedFinalResults = new TreeMap<>(dates);
-
-        System.out.println("\n");
-
-        for (Map.Entry<String, String> entry : sortedFinalResults.entrySet()) {
-            System.out.printf("Intid: \t%s\t\t\tUttid: \t%s\n",entry.getKey(),entry.getValue());
-        }
-
-
-
-        //System.out.printf("Size of dates: %s\n\n", sortedFinalResults.size());
-
-
-        // Returns a treemap that sorts the dates incrementally
-        //return new TreeMap<>(sortedFinalResults);
-    }
-    */
-
-
-
     /**
      * Retrieves information about kWh and the corresponding date
      *
@@ -376,6 +232,8 @@ public class DB {
                                                            String timestamp, String valueIDName,
                                                            int valueID, int index) throws Exception {
 
+        System.out.println("GetMan size: " + HelpingFunctions.getManMoist().size());
+
         // Initializing the data map to store the results
         Map<Integer, Map<String, Number>> finalResults = new HashMap<>();
 
@@ -399,7 +257,7 @@ public class DB {
             System.out.println(sqlStatement);
 
             // Iterating through the results
-            TableResult result = createQueryJob(sqlStatement);
+            TableResult result = HelpingFunctions.createQueryJob(sqlStatement);
 
             //System.out.println("Timestamp \t kWh");
             int baseline = 0;
@@ -417,7 +275,7 @@ public class DB {
                 // Retrieving the wanted data
                 long timeStamp = row.get("" + timestamp + "").getTimestampValue() / 1000;
                 // Riktig format, men i string
-                String formatedTimeStamp = getDateFormat().format(timeStamp);
+                String formatedTimeStamp = HelpingFunctions.getDateFormat().format(timeStamp);
 
                 // Checks for negative values
                 if (variantValue > 0) {
@@ -482,8 +340,9 @@ public class DB {
         String formatedUtTidTork = "";
         String formatedUtTidTorkPlussDays = "";
 
-        // Initializing the dates map to store the results
+        // Initializing maps to store the results
         Map<String, String> dates = new HashMap<>();
+        Map<String, String> manMoistData = new HashMap<>();
 
 
         // Defining extra parameters if required
@@ -513,10 +372,15 @@ public class DB {
 
         System.out.println(sqlStatement);
 
+        // Increments kilinID back to the correct id nr
+        if(locationID == 174){
+            kilinID += 1;
+        }
+
 
 
         // Retrieves the results from the queryjob
-        TableResult result = createQueryJob(sqlStatement);
+        TableResult result = HelpingFunctions.createQueryJob(sqlStatement);
 
         //System.out.println("InTidTork\t\t\tUtTidTork");
         // Iterating through the results
@@ -534,10 +398,10 @@ public class DB {
                     if (nameParameters[i].contains("Gran")) {
                         treeSort = "G";
                     }
-                    if (nameParameters[i].matches("\\d{2}x\\d{3}")) {
+                    if (nameParameters[i].matches("\\d+x\\d+")) {
                         String[] dimensions = nameParameters[i].split("x");
-                        width = dimensions[0];
-                        thickness = dimensions[1];
+                        thickness = dimensions[0];
+                        width = dimensions[1];
                     }
                     if (nameParameters[i].matches("\\d{1}ex")) {
                         sawSet = nameParameters[i];
@@ -559,7 +423,7 @@ public class DB {
                     long doubleValue = row.get("DryingStarted").getTimestampValue();
                     long InTidTorkLong = doubleValue/1000;
                     // Formating the data from long to a string in the correct date format
-                    formatedInTidTork = getDateFormat().format(InTidTorkLong);
+                    formatedInTidTork = HelpingFunctions.getDateFormat().format(InTidTorkLong);
                     //System.out.println("LONG!!@@@@");
                 } catch(NumberFormatException e){
                     //not long value, must be of type string
@@ -590,7 +454,7 @@ public class DB {
                 long doubleValue = row.get("DryingCompleted").getTimestampValue();
                 long utTidTorkLong = doubleValue/1000;
                 // Formating the data from long to a string in the correct date format
-                formatedUtTidTork = getDateFormat().format(utTidTorkLong);
+                formatedUtTidTork = HelpingFunctions.getDateFormat().format(utTidTorkLong);
             } catch(NumberFormatException e) {
                 //not long value, must be of type string
 
@@ -606,61 +470,62 @@ public class DB {
 
             }
 
-            //System.out.println("Inn formated: "+formatedInTidTork);
-            //System.out.println("Ut formated: "+formatedUtTidTork+"\n");
 
-            // Checks if intidtork or outtidtork is empty, if so they are ignored and not added to the list
-            if (!formatedInTidTork.isEmpty() && !formatedUtTidTork.isEmpty()){
-                // Adds the data to the dates map
-                dates.put(formatedInTidTork,formatedUtTidTork);
-            }
-            //System.out.printf("%s\t\t\t%s\n",formatedInTidTork,formatedUtTidTork);
-        }
-
-
-        // Adding days to search between
-        DateTime addedDays = new DateTime(formatedUtTidTork.split(" ")[0]);
-        formatedUtTidTorkPlussDays = addedDays.plusDays(2).toString();
-        formatedUtTidTorkPlussDays = formatedUtTidTorkPlussDays.split("T")[0];
-
-
-        // Getting manual measurement
-        // Sqlstatement
-        final String manMeasurement = "SELECT Tork, Dato ,Treslag, Tykkelse, Bredde, Planketype, Malfukt, GjeFukt " +
-                "FROM `sf-drying-optimization.174.int_ds_manfuktreg` " +
-                "WHERE Tork LIKE " + kilinID + " " +
-                "AND Treslag = " + treeSort + " " +
-                "AND Tykkelse = " + thickness + " " +
-                "AND Bredde = " + width + " " +
-                "AND Planketype = " + sawSet + " " +
-                "AND Dato BETWEEN \""+ formatedUtTidTork.split(" ")[0] +"\" AND \"" + formatedUtTidTorkPlussDays + "\" " +
-                "ORDER BY Dato";
-
-        System.out.println(manMeasurement);
 
+            // Getting manual measurement:
+            // Adding days to search between
+            String date = formatedUtTidTork.split(" ")[0];
+            DateTime addedDays = new DateTime(date);
+            formatedUtTidTorkPlussDays = addedDays.plusDays(ARJANG_NR_ADDED_DAYS).toString();
+            formatedUtTidTorkPlussDays = formatedUtTidTorkPlussDays.split("T")[0];
 
+            if (locationID == ARJANG_LOCATION_ID) {
 
-        // Retrieves the results from the queryjob
-        TableResult resultManMeasurement = createQueryJob(manMeasurement);
+                // Sqlstatement
+                final String manMeasurementStatement = "SELECT Tork, Dato, Treslag, Tykkelse, Bredde, Planketype, Diff " +
+                        "FROM `" + PROJECT_ID + "." + locationID + "." + ARJANG_MANFUKTREG + "` " +
+                        "WHERE Tork LIKE \"%" + kilinID + "%\" " +
+                        "AND Treslag LIKE \"%" + treeSort + "%\" " +
+                        "AND Tykkelse = " + thickness + " " +
+                        "AND Bredde = " + width + " " +
+                        "AND Planketype = \"" + sawSet + "\" " +
+                        "AND Dato BETWEEN \"" + date + "\" AND \"" + formatedUtTidTorkPlussDays + "\" " +
+                        "ORDER BY Dato";
 
-        //System.out.println("InTidTork\t\t\tUtTidTork");
-        // Iterating through the results
-        for (FieldValueList row : resultManMeasurement.iterateAll()) {
+                //System.out.println(manMeasurementStatement);
 
-            System.out.println(row);
 
-            moistureGoal = row.get("Malfukt").getValue().toString();
-            moistureMeasured = row.get("GjeFukt").getValue().toString();
+                // Retrieves the results from the queryjob
+                TableResult resultManMeasurement = HelpingFunctions.createQueryJob(manMeasurementStatement);
 
-        }
+                //System.out.println("InTidTork\t\t\tUtTidTork");
+                // Iterating through the results
+                for (FieldValueList manMeasurement : resultManMeasurement.iterateAll()) {
 
+                    // Checks if value is null
+                    if(!manMeasurement.get("Diff").isNull()){
+                        moistureMeasured = manMeasurement.get("Diff").getValue().toString();
+                    }
+                }
+                //System.out.println("moistureMeasured: " + moistureMeasured);
+                if(!moistureMeasured.isEmpty()){
+                    manMoistData.put(formatedUtTidTork,moistureMeasured);
+                }
+            }
 
-        System.out.println("moistureGoal: " + moistureGoal);
-        System.out.println("moistureMeasured: " + moistureMeasured);
 
 
 
+            //System.out.println("Inn formated: "+formatedInTidTork);
+            //System.out.println("Ut formated: "+formatedUtTidTork+"\n");
 
+            // Checks if intidtork or outtidtork is empty, if so they are ignored and not added to the list
+            if (!formatedInTidTork.isEmpty() && !formatedUtTidTork.isEmpty()){
+                // Adds the data to the dates map
+                dates.put(formatedInTidTork,formatedUtTidTork);
+            }
+            //System.out.printf("%s\t\t\t%s\n",formatedInTidTork,formatedUtTidTork);
+        }
 
         // Defining a treemap to sort the data incrementally
         NavigableMap<String, String> sortedFinalResults = new TreeMap<>(dates);
@@ -671,10 +536,14 @@ public class DB {
             System.out.printf("Intid: \t%s\t\t\tUttid: \t%s\n",entry.getKey(),entry.getValue());
         }
 
+        System.out.printf("Size of dates: %s\n\n", sortedFinalResults.size());
 
 
-        System.out.printf("Size of dates: %s\n\n", sortedFinalResults.size());
+        System.out.println("manMoistData size: " + manMoistData.size());
 
+        for (Map.Entry<String, String> entry : manMoistData.entrySet()) {
+            HelpingFunctions.getManMoist().put(entry.getKey(), entry.getValue());
+        }
 
         // Returns a treemap that sorts the dates incrementally
         return new TreeMap<>(sortedFinalResults);
diff --git a/src/main/java/com/application/GUI/LineChartFunctionality.java b/src/main/java/com/application/GUI/LineChartFunctionality.java
index 3352b3e..d0f1c25 100644
--- a/src/main/java/com/application/GUI/LineChartFunctionality.java
+++ b/src/main/java/com/application/GUI/LineChartFunctionality.java
@@ -49,9 +49,9 @@ public class LineChartFunctionality {
 
     private static Map<Integer, ArrayList<Double>> statistics(Map<Integer, ArrayList<Double>> multiMap){
 
-        System.out.println("\n\nMultimap: \n");
+        //System.out.println("\n\nMultimap: \n");
         for (Map.Entry<Integer, ArrayList<Double>> entry : multiMap.entrySet()) {
-            System.out.printf("\nIndex: \t%s\t\t\tkWh: \t%s\n", entry.getKey(), entry.getValue());
+            //System.out.printf("\nIndex: \t%s\t\t\tkWh: \t%s\n", entry.getKey(), entry.getValue());
 
             if(entry.getValue().size()>1){
                 SummaryStatistics stats = new SummaryStatistics();
@@ -61,10 +61,10 @@ public class LineChartFunctionality {
 
                 // Calculate 95% confidence interval
                 double ci = calcMeanCI(stats, CONFIDENCE_INTERVAL);
-                System.out.println(String.format("Mean: %f", stats.getMean()));
+                //System.out.println(String.format("Mean: %f", stats.getMean()));
                 double lower = stats.getMean() - ci;
                 double upper = stats.getMean() + ci;
-                System.out.println(String.format("Confidence Interval 95%%: %f, %f", lower, upper));
+                //System.out.println(String.format("Confidence Interval 95%%: %f, %f", lower, upper));
 
                 // Deletes entries if they are out of bounds with the confidence interval
                 entry.getValue().removeIf(value -> Double.compare(value, lower) < 0 || Double.compare(value, upper) > 0);
@@ -138,7 +138,7 @@ public class LineChartFunctionality {
         double[][] data = new double[confidenceIntervalData.size()*jMaxSize][2];
 
         int index = 0;
-        System.out.println(confidenceIntervalData);
+        //System.out.println(confidenceIntervalData);
         for (int i = 0; i < confidenceIntervalData.size(); i++) {
 
             ArrayList<Double> list = confidenceIntervalData.get(i);
@@ -181,7 +181,7 @@ public class LineChartFunctionality {
 
         for (double[] datum : data) {
             for (double v : datum) {
-                System.out.println("Data: "+v);
+                //System.out.println("Data: "+v);
             }
         }
 
@@ -191,7 +191,7 @@ public class LineChartFunctionality {
         for (int i = 0; i < confidenceIntervalData.size(); i++) {
 
             // Connect the data to a series
-            System.out.println(simpleRegression.predict(i));
+            //System.out.println(simpleRegression.predict(i));
             regressionSeries.getData().add(new XYChart.Data<String, Number>(String.valueOf(i), simpleRegression.predict(i)));
         }
         updateLineChart(regressionSeries);
diff --git a/target/classes/com/application/GUI/LineChartFunctionality.class b/target/classes/com/application/GUI/LineChartFunctionality.class
index 5af8d726187d02dc53af7c8a0c5c4932f7360d17..5114429200f1c808b85caad3617510f534fe86cf 100644
GIT binary patch
delta 3964
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

delta 4400
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

-- 
GitLab