From 0ef2781ecf9e824e25d365fe2fa1a894e0259686 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Wed, 22 Sep 2021 15:45:37 +0200
Subject: [PATCH] Add logic for performing basic aggregation

---
 index.html                |   1 +
 public/js/aggregate.js    | 168 +++++++++++++++++++++
 public/js/appChart.js     | 299 +++++++++++++++++++++-----------------
 public/js/dropDownList.js |  35 +++--
 4 files changed, 353 insertions(+), 150 deletions(-)
 create mode 100644 public/js/aggregate.js

diff --git a/index.html b/index.html
index c90ef68..03e6482 100644
--- a/index.html
+++ b/index.html
@@ -73,6 +73,7 @@
     <script defer type="module" src="js/appCesium.js"></script>
     <script defer type="module" src="js/appChart.js"></script>
     <script defer type="module" src="js/dropDownList.js"></script>
+    <script defer type="module" src="js/aggregate.js"></script>
   </head>
   <body class="sb-nav-fixed">
     <nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
diff --git a/public/js/aggregate.js b/public/js/aggregate.js
new file mode 100644
index 0000000..5157411
--- /dev/null
+++ b/public/js/aggregate.js
@@ -0,0 +1,168 @@
+"use strict";
+
+import {
+  BASE_URL,
+  QUERY_PARAMS_COMBINED,
+  getDatastreamIdFromBuildingNumber,
+  createObservationsUrl,
+  performGetRequestUsingAxios,
+  extractCombinedObservationsFromAllPages,
+} from "./appChart.js";
+
+/**
+ * Create 24-hour time strings for a time interval delimited by a start time and an end time. It is assumed that the start time is at "00:00:00" and the end time is at "23:45:00" (when the sampling rate of observations is 15 min) or "23:00:00" (when the sampling rate of observations is 60 min)
+ * @param {String} phenomenonSamplingRate The sampling rate of the phenomenon of interest represented as a string, e.g. "15 min", "60 min"
+ * @returns {Array} An array of two 24-hour strings representing the start time and end time
+ */
+const createTimeStringsForInterval = function (phenomenonSamplingRate) {
+  const fifteenMinutes = "15 min";
+  const sixtyMinutes = "60 min";
+
+  const startTime = "00:00:00";
+  const endTimeFifteenMinutes = "23:45:00";
+  const endTimeSixtyMinutes = "23:00:00";
+
+  if (
+    phenomenonSamplingRate !== fifteenMinutes &&
+    phenomenonSamplingRate !== sixtyMinutes
+  )
+    return;
+
+  // 15 min sampling rate
+  if (phenomenonSamplingRate === fifteenMinutes) {
+    return [startTime, endTimeFifteenMinutes];
+  }
+
+  // 60 min sampling rate
+  if (phenomenonSamplingRate === sixtyMinutes) {
+    return [startTime, endTimeSixtyMinutes];
+  }
+};
+
+/**
+ * Create an ISO 8601 date and time string
+ * @param {String} inputCalendarDate Calendar date string in "YYYY-MM-DD" format
+ * @param {String} inputTwentyFourHourTime 24-hour time string in "hh:mm:ss" format
+ * @returns {String} An ISO 8601 date and time string
+ */
+const createIso8601DateTimeString = function (
+  inputCalendarDate,
+  inputTwentyFourHourTime
+) {
+  return `${inputCalendarDate}T${inputTwentyFourHourTime}.000Z`;
+};
+
+/**
+ * Calculate the index of a timestamp in an array of timestamps
+ * @param {Array} inputTimestampArr An array of timestamps, extracted from an array of observations
+ * @param {String} timestampOfInterest A string representing the timestamp of interest in ISO 8601 format
+ * @returns {Number} An integer representing the index of the timestamp of interest in the array of timestamps
+ */
+const getIndexOfTimestamp = function (inputTimestampArr, timestampOfInterest) {
+  const timestampIndex = inputTimestampArr.findIndex(
+    (timestamp) => timestamp === timestampOfInterest
+  );
+
+  // If the timestamp does not exist in the timestamp array
+  if (timestampIndex === -1)
+    throw new Error(
+      "A start or end timestamp could not be found in the timestamp array"
+    );
+
+  // If the timestamp exists in the timestamp array
+  return timestampIndex;
+};
+
+/**
+ * Aggregate observations within a time interval delimited by a start date and end date. The start date may be the same as the end date.
+ * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
+ * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15 min", "60 min"
+ * @param {String} startDate A 24-hour date string representing the start date
+ * @param {String} endDate A 24-hour date string representing the end date
+ * @returns {Number} A floating-point number representing the aggregated observation value
+ */
+const aggregateObservationsWithinTimeInterval = function (
+  obsArray,
+  samplingRate,
+  startDate,
+  endDate
+) {
+  // Extract the timestamps and values from the observations
+  const obsTimestampArr = obsArray.map((obs) => obs[0]);
+  const obsValuesArr = obsArray.map((obs) => obs[1]);
+
+  // Create array of 24-hour strings for the start and end of interval
+  const startPlusEndTimeStrings = createTimeStringsForInterval(samplingRate);
+
+  // Extract 24-hour strings for the start and end of interval
+  const [startTimeString, endTimeString] = startPlusEndTimeStrings;
+
+  // Create ISO 8601 strings for the start and end of interval
+  const startIso8601DateTimeString = createIso8601DateTimeString(
+    startDate,
+    startTimeString
+  );
+  const endIso8601DateTimeString = createIso8601DateTimeString(
+    endDate,
+    endTimeString
+  );
+
+  // Calculate the indexes of the timestamps for the start and end of interval
+  const indexStartTimestamp = getIndexOfTimestamp(
+    obsTimestampArr,
+    startIso8601DateTimeString
+  );
+  const indexEndTimestamp = getIndexOfTimestamp(
+    obsTimestampArr,
+    endIso8601DateTimeString
+  );
+
+  // Extract the observations that fall within our time interval
+  const obsValuesForTimeIntervalArr = obsValuesArr.slice(
+    indexStartTimestamp,
+    indexEndTimestamp + 1
+  );
+
+  // Calculate the aggregated observation value
+  const aggregatedObsValue = obsValuesForTimeIntervalArr.reduce(
+    (accumulator, currentValue) => accumulator + currentValue
+  );
+
+  return aggregatedObsValue;
+};
+
+/**
+ * Test aggregation of observations from a single datastream
+ */
+const testAggregation = async function () {
+  // Datastream ID
+  const datastreamIdBau225VL = getDatastreamIdFromBuildingNumber(
+    "225",
+    "vl",
+    "60min"
+  );
+
+  // Observations URL
+  const observationsUrlBau225VL = createObservationsUrl(
+    BASE_URL,
+    datastreamIdBau225VL
+  );
+
+  // Observations array
+  const observationsBau225VL = await extractCombinedObservationsFromAllPages(
+    performGetRequestUsingAxios(observationsUrlBau225VL, QUERY_PARAMS_COMBINED)
+  );
+
+  // Aggregated observations
+  const observationsBau225VLAggregated =
+    aggregateObservationsWithinTimeInterval(
+      observationsBau225VL,
+      "60 min",
+      "2020-02-01",
+      "2020-03-31"
+    );
+
+  // console.log(observationsBau225VLAggregated);
+};
+
+// testAggregation();
diff --git a/public/js/appChart.js b/public/js/appChart.js
index bd1cce1..3b3a675 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -104,7 +104,7 @@ const getDatastreamIdFromBuildingNumber = function (
  * @param {Number} datastreamID Integer representing the Datastream ID
  * @returns {String} URL string for fetching a single Datastream
  */
-const getDatastreamUrl = function (baseUrl, datastreamID) {
+const createDatastreamUrl = function (baseUrl, datastreamID) {
   if (!datastreamID) return;
   const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})`;
   return fullDatastreamURL;
@@ -116,7 +116,7 @@ const getDatastreamUrl = function (baseUrl, datastreamID) {
  * @param {Number} datastreamID Integer representing the Datastream ID
  * @returns {String} URL string for fetching Observations
  */
-const getObservationsUrl = function (baseUrl, datastreamID) {
+const createObservationsUrl = function (baseUrl, datastreamID) {
   if (!datastreamID) return;
   const fullObservationsURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`;
   return fullObservationsURL;
@@ -134,7 +134,6 @@ const createTemporalFilterString = function (dateStart, dateStop) {
   return filterString;
 };
 
-// const BASE_URL_OBSERVATIONS = getObservationsUrl(80);
 const QUERY_PARAM_RESULT_FORMAT = "dataArray";
 const QUERY_PARAM_ORDER_BY = "phenomenonTime asc";
 const QUERY_PARAM_FILTER = createTemporalFilterString(
@@ -155,7 +154,7 @@ const QUERY_PARAMS_COMBINED = {
  * @param {Object} urlParamObj The URL parameters to be sent together with the GET request
  * @returns {Promise} A promise that contains the first page of results when fulfilled
  */
-const axiosGetRequest = function (urlObservations, urlParamObj) {
+const performGetRequestUsingAxios = function (urlObservations, urlParamObj) {
   return axios.get(urlObservations, {
     params: urlParamObj,
   });
@@ -172,7 +171,7 @@ const getMetadataFromSingleDatastream = async function (urlDatastream) {
     // Extract properties of interest
     const {
       data: { description, name, unitOfMeasurement },
-    } = await axiosGetRequest(urlDatastream);
+    } = await performGetRequestUsingAxios(urlDatastream);
 
     return { description, name, unitOfMeasurement };
   } catch (err) {
@@ -267,11 +266,11 @@ const formatDatastreamMetadataForChart = function (datastreamMetadata) {
 };
 
 /**
- * Format the response from SensorThings API to make it suitable for heatmap
+ * Format the response from SensorThings API to make it suitable for use in a heatmap
  * @param {Array} obsArray Array of observations (timestamp + value) that is response from SensorThings API
  * @returns {Array} Array of formatted observations suitable for use in a heatmap
  */
-const formatSTAResponseForHeatMap = function (obsArray) {
+const formatSensorThingsApiResponseForHeatMap = function (obsArray) {
   if (!obsArray) return;
 
   const dataSTAFormatted = obsArray.map((obs) => {
@@ -322,7 +321,7 @@ const calculateMinMaxValuesForHeatmapColorAxis = function (
  * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata
  * @returns {undefined} undefined
  */
-const drawHeatMapHC = function (
+const drawHeatMapHighcharts = function (
   formattedObsArrayForHeatmap,
   formattedDatastreamMetadata
 ) {
@@ -431,11 +430,11 @@ const drawHeatMapHC = function (
 };
 
 /**
- * Format the response from SensorThings API to make it suitable for line chart
+ * Format the response from SensorThings API to make it suitable for use in a line chart
  * @param {Array} obsArray Response from SensorThings API as array
  * @returns {Array} Array of formatted observations suitable for use in a line chart
  */
-const formatSTAResponseForLineChart = function (obsArray) {
+const formatSensorThingsApiResponseForLineChart = function (obsArray) {
   if (!obsArray) return;
 
   const dataSTAFormatted = obsArray.map((result) => {
@@ -453,7 +452,7 @@ const formatSTAResponseForLineChart = function (obsArray) {
  * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata
  * @returns {undefined} undefined
  */
-const drawLineChartHC = function (
+const drawLineChartHighcharts = function (
   formattedObsArrayForLineChart,
   formattedDatastreamMetadata
 ) {
@@ -537,21 +536,26 @@ const getIndexesOfUniqueObservations = function (
 /**
  * Removes observations (by modifying array in place) that are unique to a larger set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
  * @param {Array} uniqueIndexesArr An array of the indexes unique to the larger set of observations
- * @param {Array} largerObsArr The larger array of observations (timestamp + value) which is modified in place
- * @returns {undefined}
+ * @param {Array} largerObsArr The larger array of observations (timestamp + value)
+ * @returns {Array} The larger array with the unique indexes removed
  */
 const removeUniqueObservationsFromLargerArray = function (
   uniqueIndexesArr,
   largerObsArr
 ) {
-  // Reverse the indexes array so that the larger index is removed first
-  uniqueIndexesArr.reverse();
+  // Create a reversed copy of the indexes array, so that the larger index is removed first
+  const reversedUniqueIndexesArr = uniqueIndexesArr.reverse();
+
+  // Create a copy the larger observation array, will be modified in place
+  const processedLargerObsArr = largerObsArr;
 
-  uniqueIndexesArr.forEach((index) => {
+  reversedUniqueIndexesArr.forEach((index) => {
     if (index > -1) {
-      largerObsArr.splice(index, 1);
+      processedLargerObsArr.splice(index, 1);
     }
   });
+
+  return processedLargerObsArr;
 };
 
 /**
@@ -630,10 +634,12 @@ const deleteUniqueObservationsFromLargerArray = function (
   );
 
   // Remove the missing observation from the larger array of observations
-  // Modifies the array in place
-  removeUniqueObservationsFromLargerArray(indexesMissingObsArr, biggerObsArr);
+  const modifiedBiggerObsArr = removeUniqueObservationsFromLargerArray(
+    indexesMissingObsArr,
+    biggerObsArr
+  );
 
-  return [biggerObsArr, smallerObsArr];
+  return [modifiedBiggerObsArr, smallerObsArr];
 };
 
 /**
@@ -661,7 +667,7 @@ const checkForAndDeleteUniqueObservationsFromLargerArray = function (
 };
 
 /**
- * Extracts and combines observation values from two imput observation arrays of equal length
+ * Extracts and combines observation values from two input observation arrays of equal length
  * @param {Array} obsArrayOne First set of N observations (timestamp + value)
  * @param {Array} obsArrayTwo Second set of N observations (timestamp + value)
  * @returns {Array} A 2*N array of observation values from both input observation arrays
@@ -680,12 +686,15 @@ const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) {
 };
 
 /**
- * Format the response from SensorThings API to make it suitable for scatter plot
+ * Format the response from SensorThings API to make it suitable for use in a scatter plot
  * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
  * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
  * @returns {Array} Array of formatted observations suitable for use in a scatter plot
  */
-const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
+const formatSensorThingsApiResponseForScatterPlot = function (
+  obsArrayOne,
+  obsArrayTwo
+) {
   // When our observation arrays have DIFFERENT lengths
   if (obsArrayOne.length !== obsArrayTwo.length) {
     const [obsArrayOneFinal, obsArrayTwoFinal] =
@@ -708,7 +717,7 @@ const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
  * @param {*} formattedDatastreamMetadataSeriesTwo Object containing Datastream metadata for the second chart series
  * @returns {undefined}
  */
-const drawScatterPlotHC = function (
+const drawScatterPlotHighcharts = function (
   formattedObsArrayForSeriesOnePlusSeriesTwo,
   formattedDatastreamMetadataSeriesOne,
   formattedDatastreamMetadataSeriesTwo
@@ -830,61 +839,67 @@ const drawScatterPlotHC = function (
 };
 
 /**
- * Follows "@iot.nextLink" links in SensorThingsAPI's response
- * Appends new results to existing results
+ * Traverses all the pages that make up the response from a SensorThingsAPI instance. The link to the next page, if present, is denoted by the presence of a "@iot.nextLink" property in the response object. This function concatenates all the values so that the complete results are returned in one array.
  * @async
- * @param {Promise} responsePromise Promise object resulting from an Axios GET request
- * @returns {Object} Object containing results from all the "@iot.nextLink" links
+ * @param {Promise} httpGetRequestPromise Promise object resulting from an Axios GET request
+ * @returns {Promise} A promise that contains an object containing results from all the pages when fulfilled
  */
-const followNextLink = function (responsePromise) {
-  if (!responsePromise) return;
-  return responsePromise
-    .then((lastSuccess) => {
-      if (lastSuccess.data["@iot.nextLink"]) {
-        return followNextLink(
-          axios.get(lastSuccess.data["@iot.nextLink"])
-        ).then((nextLinkSuccess) => {
-          nextLinkSuccess.data.value = lastSuccess.data.value.concat(
-            nextLinkSuccess.data.value
-          );
-          return nextLinkSuccess;
-        });
-      } else {
-        return lastSuccess;
-      }
-    })
-    .catch((err) => {
-      console.error(err);
-    });
+const combineResultsFromAllPages = async function (httpGetRequestPromise) {
+  try {
+    if (!httpGetRequestPromise) return;
+
+    const lastSuccess = await httpGetRequestPromise;
+    if (lastSuccess.data["@iot.nextLink"]) {
+      const nextLinkSuccess = await combineResultsFromAllPages(
+        axios.get(lastSuccess.data["@iot.nextLink"])
+      );
+      nextLinkSuccess.data.value = lastSuccess.data.value.concat(
+        nextLinkSuccess.data.value
+      );
+      return nextLinkSuccess;
+    } else {
+      return lastSuccess;
+    }
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
- * Retrieve all the Observations from a Datastream after traversing all the "@iot.nextLink" links
+ * Traverses all the pages that make up the response from a SensorThingsAPI instance and extracts the combined Observations
  * @async
  * @param {Promise} httpGetRequestPromise Promise object resulting from an Axios GET request
- * @returns {Promise} A promise that contains an array of Observations from a single Datastream when fulfilled
+ * @returns {Promise} A promise that contains an array of Observations when fulfilled
  */
-const getCombinedObservationsFromAllNextLinks = function (
+const extractCombinedObservationsFromAllPages = async function (
   httpGetRequestPromise
 ) {
-  return followNextLink(httpGetRequestPromise)
-    .then((success) => {
-      const successValue = success.data.value;
-      // Array that will hold the combined observations
-      const combinedObservations = [];
-      successValue.forEach((dataObj) => {
-        // Each page of results will have a dataArray that holds the observations
-        const dataArrays = dataObj.dataArray;
-        combinedObservations.push(...dataArrays);
-      });
-
-      return new Promise((resolve, reject) => {
-        resolve(combinedObservations);
-      });
-    })
-    .catch((err) => {
-      console.error(err);
+  try {
+    const successResponse = await combineResultsFromAllPages(
+      httpGetRequestPromise
+    );
+
+    // Extract value array from the success response object
+    const {
+      data,
+      data: { value: valueArr },
+    } = successResponse;
+
+    // Array that will hold the combined observations
+    const combinedObservations = [];
+
+    valueArr.forEach((val) => {
+      // Each page of results will have a dataArray that holds the observations
+      const { dataArray } = val;
+      combinedObservations.push(...dataArray);
+    });
+
+    return new Promise((resolve, reject) => {
+      resolve(combinedObservations);
     });
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
@@ -959,18 +974,18 @@ const getMetadataPlusObservationsFromMultipleDatastreams = async function (
 
     // Observations URLs
     const observationsUrlArr = datastreamsIdsArr.map((datastreamId) =>
-      getObservationsUrl(BASE_URL, datastreamId)
+      createObservationsUrl(BASE_URL, datastreamId)
     );
 
     // Datastreams URLs
     const datastreamsUrlArr = datastreamsIdsArr.map((datastreamId) =>
-      getDatastreamUrl(BASE_URL, datastreamId)
+      createDatastreamUrl(BASE_URL, datastreamId)
     );
 
     // Promise objects - Observations
     const observationsPromisesArr = observationsUrlArr.map((obsUrl) =>
-      getCombinedObservationsFromAllNextLinks(
-        axiosGetRequest(obsUrl, QUERY_PARAMS_COMBINED)
+      extractCombinedObservationsFromAllPages(
+        performGetRequestUsingAxios(obsUrl, QUERY_PARAMS_COMBINED)
       )
     );
 
@@ -995,73 +1010,86 @@ const getMetadataPlusObservationsFromMultipleDatastreams = async function (
  * @async
  * @param {String} buildingId The building ID as a string
  * @param {String} samplingRate The sampling rate as a string
- * @returns A promise that contains an array (that is made up of a temperature difference array and a metadata object) when fulfilled
+ * @returns {Promise} A promise that contains an array (that is made up of a temperature difference array and a metadata object) when fulfilled
  */
 const calculateVorlaufMinusRuecklaufTemperature = async function (
   buildingId,
   samplingRate
 ) {
-  const bldgSensorSamplingRateArr = [
-    [buildingId, "vl", samplingRate],
-    [buildingId, "rl", samplingRate],
-  ];
-
-  const BUILDING_ID = buildingId;
-  const SAMPLING_RATE = samplingRate;
+  try {
+    const bldgSensorSamplingRateArr = [
+      [buildingId, "vl", samplingRate],
+      [buildingId, "rl", samplingRate],
+    ];
 
-  const observationsPlusMetadata =
-    await getMetadataPlusObservationsFromMultipleDatastreams(
-      bldgSensorSamplingRateArr
-    );
+    const BUILDING_ID = buildingId;
+    const SAMPLING_RATE = samplingRate;
 
-  // Extract Vorlauf temperature and Ruecklauf temperature
-  const [[vorlaufTemp, ruecklaufTemp], [metadataVorlauf, metadataRuecklauf]] =
-    observationsPlusMetadata;
+    const observationsPlusMetadata =
+      await getMetadataPlusObservationsFromMultipleDatastreams(
+        bldgSensorSamplingRateArr
+      );
 
-  const vorlaufTempValues = vorlaufTemp.map((obs) => obs[1]);
-  const ruecklaufTempValues = ruecklaufTemp.map((obs) => obs[1]);
+    // Extract Vorlauf temperature, Ruecklauf temperature and metadata
+    const [[vorlaufTemp, ruecklaufTemp], [metadataVorlauf, metadataRuecklauf]] =
+      observationsPlusMetadata;
 
-  // The arrays have equal length, we need only use one of them for looping
-  // Resulting array contains the following pairs (timestamp + dT)
-  const vorlaufMinusRuecklaufTemp = vorlaufTemp.map((obs, i) => [
-    obs[0],
-    vorlaufTempValues[i] - ruecklaufTempValues[i],
-  ]);
+    // Extract the temperature values
+    const vorlaufTempValues = vorlaufTemp.map((obs) => obs[1]);
+    const ruecklaufTempValues = ruecklaufTemp.map((obs) => obs[1]);
 
-  // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
-  const { name: datastreamNameVorlauf, unitOfMeasurement } = metadataVorlauf;
+    // The arrays have equal length, we need only use one of them for looping
+    // Resulting array contains the following pairs (timestamp + dT)
+    const vorlaufMinusRuecklaufTemp = vorlaufTemp.map((obs, i) => [
+      obs[0],
+      vorlaufTempValues[i] - ruecklaufTempValues[i],
+    ]);
 
-  // From Ruecklauf metadata, extract `name`
-  const { name: datastreamNameRuecklauf } = metadataRuecklauf;
+    // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
+    const {
+      name: datastreamNameVorlauf,
+      unitOfMeasurement: unitOfMeasurementVorlauf,
+    } = metadataVorlauf;
 
-  // Extract the phenomenon names from Datastream names
-  const phenomenonNameVorlauf = extractPhenomenonNameFromDatastreamName(
-    datastreamNameVorlauf
-  );
-  const phenomenonNameRuecklauf = extractPhenomenonNameFromDatastreamName(
-    datastreamNameRuecklauf
-  );
+    // From Ruecklauf metadata, extract `name`
+    const { name: datastreamNameRuecklauf } = metadataRuecklauf;
 
-  // Create our custom datastream description text
-  const descriptionCombined = `Computed dT: ${phenomenonNameVorlauf} minus ${phenomenonNameRuecklauf}`;
+    // Extract the phenomenon names from the Datastream names
+    const phenomenonNameVorlauf = extractPhenomenonNameFromDatastreamName(
+      datastreamNameVorlauf
+    );
+    const phenomenonNameRuecklauf = extractPhenomenonNameFromDatastreamName(
+      datastreamNameRuecklauf
+    );
 
-  // The resulting datastream description string has two `temperature` substrings;
-  // replace the first occurence with an empty string
-  const description = descriptionCombined.replace("temperature", "");
+    // Create our custom datastream description text
+    // The resulting datastream description string has two `temperature` substrings;
+    // replace the first occurence with an empty string
+    const descriptionTempDifference =
+      `Computed dT: ${phenomenonNameVorlauf} minus ${phenomenonNameRuecklauf}`.replace(
+        "temperature",
+        ""
+      );
 
-  // Create our custom datastream name text
-  const name = `BOSCH_${BUILDING_ID} / dT Temperature difference (VL-RL) DS:${SAMPLING_RATE}`;
+    // Create our custom datastream name text
+    const nameTempDifference = `BOSCH_${BUILDING_ID} / dT Temperature difference (VL-RL) DS:${SAMPLING_RATE}`;
 
-  return [
-    vorlaufMinusRuecklaufTemp,
+    // The datastream object that we return needs to have these property names
+    const description = descriptionTempDifference;
+    const name = nameTempDifference;
+    const unitOfMeasurement = unitOfMeasurementVorlauf;
 
-    // The datastream metadata object needs to have these property names
-    {
-      description,
-      name,
-      unitOfMeasurement,
-    },
-  ];
+    return [
+      vorlaufMinusRuecklaufTemp,
+      {
+        description,
+        name,
+        unitOfMeasurement,
+      },
+    ];
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
@@ -1071,8 +1099,8 @@ const drawHeatmapHCUsingTempDifference = async function () {
   const [tempDifferenceObsArrBau225, tempDifferenceMetadataBau225] =
     await calculateVorlaufMinusRuecklaufTemperature("225", "60min");
 
-  drawHeatMapHC(
-    formatSTAResponseForHeatMap(tempDifferenceObsArrBau225),
+  drawHeatMapHighcharts(
+    formatSensorThingsApiResponseForHeatMap(tempDifferenceObsArrBau225),
     formatDatastreamMetadataForChart(tempDifferenceMetadataBau225)
   );
 };
@@ -1099,8 +1127,11 @@ const drawScatterPlotHCTest2 = async function () {
     [metadataSensorOne, metadataSensorTwo],
   ] = observationsPlusMetadata;
 
-  drawScatterPlotHC(
-    formatSTAResponseForScatterPlot(obsSensorOneArr, obsSensorTwoArr),
+  drawScatterPlotHighcharts(
+    formatSensorThingsApiResponseForScatterPlot(
+      obsSensorOneArr,
+      obsSensorTwoArr
+    ),
     formatDatastreamMetadataForChart(metadataSensorOne),
     formatDatastreamMetadataForChart(metadataSensorTwo)
   );
@@ -1115,16 +1146,16 @@ export {
   BASE_URL,
   QUERY_PARAMS_COMBINED,
   getDatastreamIdFromBuildingNumber,
-  getDatastreamUrl,
-  getObservationsUrl,
+  createDatastreamUrl,
+  createObservationsUrl,
   createTemporalFilterString,
-  axiosGetRequest,
+  performGetRequestUsingAxios,
   getMetadataFromSingleDatastream,
   formatDatastreamMetadataForChart,
-  formatSTAResponseForHeatMap,
-  drawHeatMapHC,
-  formatSTAResponseForLineChart,
-  drawLineChartHC,
-  getCombinedObservationsFromAllNextLinks,
+  formatSensorThingsApiResponseForHeatMap,
+  drawHeatMapHighcharts,
+  formatSensorThingsApiResponseForLineChart,
+  drawLineChartHighcharts,
+  extractCombinedObservationsFromAllPages,
   getMetadataPlusObservationsFromSingleDatastream,
 };
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index 5281763..e069f2b 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -4,16 +4,16 @@ import {
   BASE_URL,
   QUERY_PARAMS_COMBINED,
   getDatastreamIdFromBuildingNumber,
-  getDatastreamUrl,
-  getObservationsUrl,
-  axiosGetRequest,
+  createDatastreamUrl,
+  createObservationsUrl,
+  performGetRequestUsingAxios,
   getMetadataFromSingleDatastream,
   formatDatastreamMetadataForChart,
-  formatSTAResponseForHeatMap,
-  drawHeatMapHC,
-  formatSTAResponseForLineChart,
-  drawLineChartHC,
-  getCombinedObservationsFromAllNextLinks,
+  formatSensorThingsApiResponseForHeatMap,
+  drawHeatMapHighcharts,
+  formatSensorThingsApiResponseForLineChart,
+  drawLineChartHighcharts,
+  extractCombinedObservationsFromAllPages,
   getMetadataPlusObservationsFromSingleDatastream,
 } from "./appChart.js";
 
@@ -285,14 +285,17 @@ const selectChartTypeFromDropDown = async function () {
     // Display the loading indicator
     showLoadingSpinner();
 
-    const URL_DATASTREAM = getDatastreamUrl(BASE_URL, selectedDatastream);
-    const URL_OBSERVATIONS = getObservationsUrl(BASE_URL, selectedDatastream);
+    const URL_DATASTREAM = createDatastreamUrl(BASE_URL, selectedDatastream);
+    const URL_OBSERVATIONS = createObservationsUrl(
+      BASE_URL,
+      selectedDatastream
+    );
 
     // Create promises
     const promiseDatastreamMetadata =
       getMetadataFromSingleDatastream(URL_DATASTREAM);
-    const promiseCombinedObservations = getCombinedObservationsFromAllNextLinks(
-      axiosGetRequest(URL_OBSERVATIONS, QUERY_PARAMS_COMBINED)
+    const promiseCombinedObservations = extractCombinedObservationsFromAllPages(
+      performGetRequestUsingAxios(URL_OBSERVATIONS, QUERY_PARAMS_COMBINED)
     );
 
     // Pass promises to our async function
@@ -307,13 +310,13 @@ const selectChartTypeFromDropDown = async function () {
     const datastreamMetadata = metadataPlusObservations[1];
 
     if (selectedChartType === "Line") {
-      drawLineChartHC(
-        formatSTAResponseForLineChart(combinedObs),
+      drawLineChartHighcharts(
+        formatSensorThingsApiResponseForLineChart(combinedObs),
         formatDatastreamMetadataForChart(datastreamMetadata)
       );
     } else if (selectedChartType === "Heatmap") {
-      drawHeatMapHC(
-        formatSTAResponseForHeatMap(combinedObs),
+      drawHeatMapHighcharts(
+        formatSensorThingsApiResponseForHeatMap(combinedObs),
         formatDatastreamMetadataForChart(datastreamMetadata)
       );
     }
-- 
GitLab