diff --git a/public/js/appChart.js b/public/js/appChart.js
index 338efe4c53a2cb61a47f6a1c279ccac659c9e914..7e2cc390c758358da14372ded98be3f06713c102 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -25,11 +25,12 @@ import {
   drawColumnChartHighcharts,
 } from "./src_modules/chartColumn.mjs";
 
+import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs";
+
 import {
   formatDatastreamMetadataForChart,
   extractPropertiesFromFormattedDatastreamMetadata,
-  getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
-} from "./src_modules/fetchData.mjs";
+} from "./src_modules/fetchedDataProcess.mjs";
 
 import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs";
 
@@ -73,13 +74,14 @@ const drawHeatmapHCUsingTempDifference = async function () {
     // Format the metadata
     const formattedTempDiff225MetadataNestedArr =
       metadataTemperatureDiff225NestedArr.map((metadataObj) =>
-        formatDatastreamMetadataForChart(metadataObj)
+        formatDatastreamMetadataForChart(metadataObj, false)
       );
 
     // Extract the formatted metadata properties
     const extractedFormattedTempDiff225Properties =
       extractPropertiesFromFormattedDatastreamMetadata(
-        formattedTempDiff225MetadataNestedArr
+        formattedTempDiff225MetadataNestedArr,
+        false
       );
 
     // First need to extract the formatted observations from the nested array
@@ -127,13 +129,14 @@ const drawScatterPlotHCTest2 = async function () {
 
     // Create formatted array(s) for metadata
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
+      formatDatastreamMetadataForChart(metadataObj, false)
     );
 
     // Extract the formatted metadata properties
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr
+        formattedMetadataNestedArr,
+        false
       );
 
     drawScatterPlotHighcharts(
@@ -175,13 +178,14 @@ const testLineChartMultipleSeries = async function () {
 
     // Format the metadata
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
-      formatDatastreamMetadataForChart(metadataArr)
+      formatDatastreamMetadataForChart(metadataArr, false)
     );
 
     // Extract the formatted metadata properties
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr
+        formattedMetadataNestedArr,
+        false
       );
 
     drawLineChartHighcharts(
@@ -260,13 +264,16 @@ const drawColumnChartMonthlySumTest = async function () {
 
     // Format the metadata
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
+      formatDatastreamMetadataForChart(metadataObj, true)
     );
 
     // Extract the formatted metadata properties
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr
+        formattedMetadataNestedArr,
+        true,
+        "monthly",
+        "sum"
       );
 
     drawColumnChartHighcharts(
@@ -339,13 +346,16 @@ const drawColumnChartDailySumTest = async function () {
 
     // Format the metadata
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
+      formatDatastreamMetadataForChart(metadataObj, true)
     );
 
     // Extract the formatted metadata properties
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr
+        formattedMetadataNestedArr,
+        true,
+        "daily",
+        "sum"
       );
 
     drawColumnChartHighcharts(
@@ -423,14 +433,17 @@ const drawLineChartMonthlyAverageTest = async function () {
       );
 
     // Format the metadata
+    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
+      formatDatastreamMetadataForChart(metadataObj, false)
     );
 
     // Extract the formatted metadata properties
+    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr
+        formattedMetadataNestedArr,
+        false
       );
 
     drawLineChartHighcharts(
@@ -502,14 +515,17 @@ const drawLineChartDailyAverageTest = async function () {
       );
 
     // Format the metadata
+    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
+      formatDatastreamMetadataForChart(metadataObj, false)
     );
 
     // Extract the formatted metadata properties
+    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr
+        formattedMetadataNestedArr,
+        false
       );
 
     drawLineChartHighcharts(
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index e498776f2dede2f9f30e4fda1b3ad0bdf2b1cd93..c765c8b5f758a3f4af1e4cc7811732ca5e2a3e16 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -5,11 +5,12 @@ import {
   QUERY_PARAMS_COMBINED,
 } from "./src_modules/baseUrlPlusQueryParams.mjs";
 
+import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs";
+
 import {
   formatDatastreamMetadataForChart,
   extractPropertiesFromFormattedDatastreamMetadata,
-  getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
-} from "./src_modules/fetchData.mjs";
+} from "./src_modules/fetchedDataProcess.mjs";
 
 import {
   formatSensorThingsApiResponseForLineChart,
@@ -84,7 +85,7 @@ const getUniqueValues = function (dataArr, index) {
 
 /**
  * Populate the HTML elements that make up a drop down list
- * @param {String} element String corresponding to the ID of a drop down HTML element
+ * @param {Object} element HTMLElement object of a drop down list element
  * @param {Array} uniqueValuesArr An array of unique values
  * @returns {undefined}
  */
@@ -100,8 +101,8 @@ const populateDropDown = function (element, uniqueValuesArr) {
 
 /**
  * Filter an array using filter strings of variable length
- * @param {*} dataArr Input array
- * @param {*} filtersAsArray An array of filter strings
+ * @param {Array} dataArr Input array
+ * @param {Array} filtersAsArray An array of filter strings
  * @returns {Array} An array that contains the filter result
  */
 const filterArray = function (dataArr, filtersAsArray) {
@@ -112,9 +113,9 @@ const filterArray = function (dataArr, filtersAsArray) {
 
 /**
  * Create a drop down list in the HTML document
- * @param {*} dataArr Input array
- * @param {*} filtersAsArray An array of strings tp be used as filters
- * @param {*} targetElement String corresponding to the ID of a drop down HTML element
+ * @param {Array} dataArr Input array
+ * @param {Array} filtersAsArray An array of strings tp be used as filters
+ * @param {Object} targetElement HTMLElement object of a drop down list element
  * @returns {undefined}
  */
 const makeDropDown = function (dataArr, filtersAsArray, targetElement) {
@@ -292,12 +293,15 @@ const selectChartTypeFromDropDown = async function () {
 
     // Create formatted array(s) for metadata - same for both chart types
     const formattedMetadataArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
+      formatDatastreamMetadataForChart(metadataObj, false)
     );
 
     // Extract the formatted metadata properties
     const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(formattedMetadataArr);
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataArr,
+        false
+      );
 
     if (selectedChartType === "Line") {
       drawLineChartHighcharts(
diff --git a/public/js/src_modules/aggregate.mjs b/public/js/src_modules/aggregate.mjs
index 1174ffc0543100212d46fe6eec644ca40efae4a2..6691c1c3f1838999a8ae84d78277a94ae46df64a 100644
--- a/public/js/src_modules/aggregate.mjs
+++ b/public/js/src_modules/aggregate.mjs
@@ -14,7 +14,7 @@ const calculateSumOfObservationValuesWithinDatesInterval = function (
   obsValuesForDaysIntervalArr
 ) {
   return obsValuesForDaysIntervalArr.reduce(
-    (accumulator, currentValue) => accumulator + currentValue
+    (accumulator, obsValue) => accumulator + obsValue
   );
 };
 
@@ -42,7 +42,7 @@ const calculateSumOfObservationValuesWithinMonthInterval = function (
   obsValuesForMonthIntervalArr
 ) {
   return obsValuesForMonthIntervalArr.reduce(
-    (accumulator, currentValue) => accumulator + currentValue
+    (accumulator, obsValue) => accumulator + obsValue
   );
 };
 
diff --git a/public/js/src_modules/calculateTemperatureDiff.mjs b/public/js/src_modules/calculateTemperatureDiff.mjs
index afc2aac60547935f5e5471384c0077578a5579fa..157ae45ffb1d73285aa46c06a033578fe948e46d 100644
--- a/public/js/src_modules/calculateTemperatureDiff.mjs
+++ b/public/js/src_modules/calculateTemperatureDiff.mjs
@@ -1,9 +1,8 @@
 "use strict";
 
-import {
-  extractPhenomenonNameFromDatastreamName,
-  getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
-} from "./fetchData.mjs";
+import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fetchData.mjs";
+
+import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcess.mjs";
 
 /**
  * Calculate the temperature difference, dT, between Vorlauf temperature [VL] and RÞcklauf temperature [RL] (i.e., dT = VL - RL)
diff --git a/public/js/src_modules/chartColumn.mjs b/public/js/src_modules/chartColumn.mjs
index 1da6590c29060ef74d51c77b60e1e840903958d3..cb0b39e406a53996b37d177b4cd56f997b98abe1 100644
--- a/public/js/src_modules/chartColumn.mjs
+++ b/public/js/src_modules/chartColumn.mjs
@@ -1,6 +1,12 @@
 "use strict";
 
-import { chartExportOptions } from "./chartHelpers.mjs";
+import {
+  chartExportOptions,
+  createCombinedTextDelimitedByComma,
+  extractSamplingRateFromDatastreamName,
+} from "./chartHelpers.mjs";
+
+import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcess.mjs";
 
 /**
  * Format a computed aggregation result to make it suitable for a column chart
@@ -30,20 +36,19 @@ const formatAggregationResultForColumnChart = function (
 /**
  * Creates an options object for each series drawn in a column chart
  * @param {Array} formattedAggregatedResultForColumnChart An array of formatted aggregated result array(s) from one or more datastreams
- * @param {Array} phenomenonNamesArr An array of phenomenon name(s)
- * @param {Array} phenomenonSymbolsArr An array of phenomenon symbol(s)
+ * @param {Array} buildingIdsPhenomenonNamesArr An array of string(s) made up of building ID(s) + phenomenon name(s)
  * @returns {Array} An array made up of series options object(s)
  */
 const createSeriesOptionsForColumnChart = function (
   formattedAggregatedResultForColumnChart,
-  phenomenonNamesArr,
-  phenomenonSymbolsArr
+  buildingIdsPhenomenonNamesArr
 ) {
   // Create an array of seriesOptions objects
   // Assumes that the observation array of arrays, phenomenon names array and phenomenon symbols array are of equal length
   // Use one of the arrays for looping
   if (
-    formattedAggregatedResultForColumnChart.length !== phenomenonNamesArr.length
+    formattedAggregatedResultForColumnChart.length !==
+    buildingIdsPhenomenonNamesArr.length
   )
     throw new Error(
       "The observations array and phenomenon names array have different lengths"
@@ -52,7 +57,7 @@ const createSeriesOptionsForColumnChart = function (
   return formattedAggregatedResultForColumnChart.map(
     (formattedAggResArray, i) => {
       return {
-        name: `${phenomenonNamesArr[i]} (${phenomenonSymbolsArr[i]})`,
+        name: `${buildingIdsPhenomenonNamesArr[i]}`,
         data: formattedAggResArray,
         turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
       };
@@ -60,6 +65,64 @@ const createSeriesOptionsForColumnChart = function (
   );
 };
 
+/**
+ * Creates a date string that is used as a header for a shared tooltip string for a column chart
+ * @param {Number} pointXAxisValue The x-axis value (Unix timestamp) which is common for a set of data points
+ * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
+ * @returns {String} A calendar date or calendar month string that is common for a set of data points
+ */
+const createHeaderDateString = function (pointXAxisValue, aggregationInterval) {
+  if (aggregationInterval === "daily")
+    return `${Highcharts.dateFormat("%A, %b %e, %Y", pointXAxisValue)}`;
+  else if (aggregationInterval === "monthly")
+    return `${Highcharts.dateFormat("%b %Y", pointXAxisValue)}`;
+};
+
+/**
+ * Create a partial string for a column's chart title
+ * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
+ * @param {String} aggregationType The aggregation type as a string, either "sum" or "average"
+ * @returns {String} Partial string for chart title
+ */
+const createPartialTitleForColumnChart = function (
+  aggregationInterval,
+  aggregationType
+) {
+  // Case 1: No aggregation; return empty string
+  if (!aggregationInterval && !aggregationType) return ``;
+
+  // Case 2: Aggregation; capitalize the first characters
+  return `${
+    aggregationInterval.slice(0, 1).toUpperCase() + aggregationInterval.slice(1)
+  } ${aggregationType.slice(0, 1).toUpperCase() + aggregationType.slice(1)}`;
+};
+
+/**
+ * Create a full string for a column's chart title
+ * @param {Array} datastreamNamesArr An array of datastream names as strings
+ * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
+ * @param {String} aggregationType The aggregation type as a string, either "sum" or "average"
+ * @returns {String} Full string for chart title
+ */
+const createFullTitleForColumnChart = function (
+  phenomenonNamesArr,
+  aggregationInterval,
+  aggregationType
+) {
+  // Case 1: No aggregation; create a comma separated string of phenomenon names
+  if (!aggregationInterval && !aggregationType)
+    return `${createPartialTitleForColumnChart(
+      aggregationInterval,
+      aggregationType
+    )}${createCombinedTextDelimitedByComma(phenomenonNamesArr)}`;
+
+  // Case 2: Aggregation
+  return `${createPartialTitleForColumnChart(
+    aggregationInterval,
+    aggregationType
+  )} ${phenomenonNamesArr[0]}`;
+};
+
 /**
  * Draw a column chart using Highcharts library
  * @param {Array} formattedAggResultArraysForColumnChart An array made up of formatted aggregated result array(s) suitable for use in a column chart
@@ -73,16 +136,39 @@ const drawColumnChartHighcharts = function (
   // Arrays of datastream properties
   const {
     datastreamNamesArr,
-    phenomenonNamesArr,
+    buildingIdsPhenomenonNamesArr,
     unitOfMeasurementSymbolsArr,
+    aggregationInterval,
+    aggregationType,
   } = extractedFormattedDatastreamProperties;
 
+  // Create an array of phenomenon names
+  const phenomenonNamesArr = datastreamNamesArr.map((datastreamName) =>
+    extractPhenomenonNameFromDatastreamName(datastreamName)
+  );
+
+  // Create the array of series options object(s)
   const seriesOptionsArr = createSeriesOptionsForColumnChart(
     formattedAggResultArraysForColumnChart,
+    buildingIdsPhenomenonNamesArr
+  );
+
+  // Assume that we will be comparing similar phenomena, so we can use the first phenomenon name
+  const phenomenonName = phenomenonNamesArr[0];
+
+  // Assume that we will be comparing similar phenomena, so we can use the first phenomenon symbol
+  const unitOfMeasurementSymbol = unitOfMeasurementSymbolsArr[0];
+
+  const textChartTitle = createFullTitleForColumnChart(
     phenomenonNamesArr,
-    unitOfMeasurementSymbolsArr
+    aggregationInterval,
+    aggregationType
   );
 
+  const textChartSubtitle = `Sampling rate(s): ${createCombinedTextDelimitedByComma(
+    extractSamplingRateFromDatastreamName(datastreamNamesArr)
+  )}`;
+
   Highcharts.chart("chart-column", {
     chart: {
       type: "column",
@@ -90,11 +176,11 @@ const drawColumnChartHighcharts = function (
     },
 
     title: {
-      text: "Monthly Average Rainfall",
+      text: textChartTitle,
     },
 
     subtitle: {
-      text: "Source: WorldClimate.com",
+      text: textChartSubtitle,
     },
 
     xAxis: {
@@ -110,21 +196,23 @@ const drawColumnChartHighcharts = function (
     },
 
     tooltip: {
-      headerFormat: `
-                      <span style="font-size:10px">{point.key}</span>
-                      <table>
-                      `,
-      pointFormat: `
-                      <tr>
-                          <td style="color:{series.color};padding:0">{series.name}: </td>
-                          <td style="padding:0"><b>{point.y:.1f} mm</b></td>
-                      </tr>
-                      `,
-      footerFormat: `
-                      </table>
-                      `,
+      formatter() {
+        // Our tooltip is shared
+        // this.x -- common for all points
+        // this.points -- an array containing properties for each series
+        // Note that our `reduce` method is in this format:
+        // ((accumulator, currentValue, currentIndex) => {...}, initialValue)
+        return this.points.reduce(
+          (accumulator, point, i) =>
+            `${accumulator} <br/> <span style="color:${point.color}">${
+              point.series.name
+            }</span>: <b>${point.y.toFixed(2)} ${
+              unitOfMeasurementSymbolsArr[i]
+            }</b>`,
+          `${createHeaderDateString(this.x, aggregationInterval)}`
+        );
+      },
       shared: true,
-      useHTML: true,
     },
 
     plotOptions: {
diff --git a/public/js/src_modules/fetchData.mjs b/public/js/src_modules/fetchData.mjs
index 0311398ccdcb8ab4e088692357667ff38e95f137..9064ed667435dffc55dcc2d7faa5bf408a385f63 100644
--- a/public/js/src_modules/fetchData.mjs
+++ b/public/js/src_modules/fetchData.mjs
@@ -81,99 +81,6 @@ const getMetadataFromMultipleDatastreams = async function (datastreamsUrlArr) {
   }
 };
 
-/**
- * Match the unitOfMeasurement's string representation of a symbol to an actual symbol, where necessary
- * @param {String} unitOfMeasurementSymbolString String representation of the unitOfMeasurement's symbol
- * @returns {String} The unitOfMeasurement's symbol
- */
-const matchUnitOfMeasurementSymbolStringToSymbol = function (
-  unitOfMeasurementSymbolString
-) {
-  const unicodeCodePointDegreeSymbol = "\u00B0";
-  const unicodeCodePointSuperscriptThree = "\u00B3";
-
-  if (unitOfMeasurementSymbolString === "degC")
-    return `${unicodeCodePointDegreeSymbol}C`;
-
-  if (unitOfMeasurementSymbolString === "m3/h")
-    return `m${unicodeCodePointSuperscriptThree}/h`;
-
-  // If no symbol exists
-  return unitOfMeasurementSymbolString;
-};
-
-/**
- * Extract the phenomenon name from a Datastream's name
- * @param {String} datastreamName A string representing the Datastream's name
- * @returns {String} The extracted phenomenon name
- */
-const extractPhenomenonNameFromDatastreamName = function (datastreamName) {
-  const regex = /\/ (.*) DS/;
-  return datastreamName.match(regex)[1]; // use second element in array
-};
-
-/**
- * Format the response containing a Datastream's metadata from Sensorthings API
- * @param {Object} datastreamMetadata An object containing a Datastream's metadata
- * @returns {Object} An object containing the formatted metadata that is suitable for use in a line chart or heatmap
- */
-const formatDatastreamMetadataForChart = function (datastreamMetadata) {
-  const {
-    description: datastreamDescription,
-    name: datastreamName,
-    unitOfMeasurement,
-  } = datastreamMetadata;
-
-  // Extract phenomenon name from Datastream name
-  const phenomenonName =
-    extractPhenomenonNameFromDatastreamName(datastreamName);
-
-  // Get the unitOfMeasurement's symbol
-  const unitOfMeasurementSymbol = matchUnitOfMeasurementSymbolStringToSymbol(
-    unitOfMeasurement.symbol
-  );
-
-  return {
-    datastreamDescription,
-    datastreamName,
-    phenomenonName,
-    unitOfMeasurementSymbol,
-  };
-};
-
-/**
- * Extract the properties that make up the formatted datastream metadata object(s)
- * @param {Array} formattedDatastreamsMetadataArr An array of formatted metadata object(s) from one or more datastreams
- * @returns {Object} An object that contains array(s) of formatted datastream metadata properties
- */
-const extractPropertiesFromFormattedDatastreamMetadata = function (
-  formattedDatastreamsMetadataArr
-) {
-  // Create arrays from the properties of the formatted datastream metadata
-  const datastreamDescriptionsArr = formattedDatastreamsMetadataArr.map(
-    (datastreamMetadata) => datastreamMetadata.datastreamDescription
-  );
-
-  const datastreamNamesArr = formattedDatastreamsMetadataArr.map(
-    (datastreamMetadata) => datastreamMetadata.datastreamName
-  );
-
-  const phenomenonNamesArr = formattedDatastreamsMetadataArr.map(
-    (datastreamMetadata) => datastreamMetadata.phenomenonName
-  );
-
-  const unitOfMeasurementSymbolsArr = formattedDatastreamsMetadataArr.map(
-    (datastreamMetadata) => datastreamMetadata.unitOfMeasurementSymbol
-  );
-
-  return {
-    datastreamDescriptionsArr,
-    datastreamNamesArr,
-    phenomenonNamesArr,
-    unitOfMeasurementSymbolsArr,
-  };
-};
-
 /**
  * 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
@@ -321,9 +228,4 @@ const getMetadataPlusObservationsFromSingleOrMultipleDatastreams =
     }
   };
 
-export {
-  extractPhenomenonNameFromDatastreamName,
-  formatDatastreamMetadataForChart,
-  extractPropertiesFromFormattedDatastreamMetadata,
-  getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
-};
+export { getMetadataPlusObservationsFromSingleOrMultipleDatastreams };
diff --git a/public/js/src_modules/fetchedDataProcess.mjs b/public/js/src_modules/fetchedDataProcess.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..e766b5ebe5cc312be93c9e71b4612384d4092e56
--- /dev/null
+++ b/public/js/src_modules/fetchedDataProcess.mjs
@@ -0,0 +1,195 @@
+"use strict";
+
+/**
+ * Match the unitOfMeasurement's string representation of a symbol to an actual symbol, where necessary
+ * @param {String} unitOfMeasurementSymbolString String representation of the unitOfMeasurement's symbol
+ * @returns {String} The unitOfMeasurement's symbol
+ */
+const matchUnitOfMeasurementSymbolStringToSymbol = function (
+  unitOfMeasurementSymbolString
+) {
+  const unicodeCodePointDegreeSymbol = "\u00B0";
+  const unicodeCodePointSuperscriptThree = "\u00B3";
+
+  if (unitOfMeasurementSymbolString === "degC")
+    return `${unicodeCodePointDegreeSymbol}C`;
+
+  if (unitOfMeasurementSymbolString === "m3/h")
+    return `m${unicodeCodePointSuperscriptThree}/h`;
+
+  // If no symbol exists
+  return unitOfMeasurementSymbolString;
+};
+
+/**
+ * Extract the phenomenon name from a Datastream's name
+ * @param {String} datastreamName A string representing the Datastream's name
+ * @returns {String} The extracted phenomenon name
+ */
+const extractPhenomenonNameFromDatastreamName = function (datastreamName) {
+  const regex = /\/ (.*) DS/;
+  return datastreamName.match(regex)[1]; // use second element in array
+};
+
+/**
+ * Extract the building Id and phenomenon name from a Datastream's name
+ * @param {String} datastreamName A string representing the Datastream's name
+ * @returns {String} The extracted building ID and phenomenon name
+ */
+const extractBuildingIdPhenomenonNameFromDatastreamName = function (
+  datastreamName
+) {
+  // The negative index should remove these nine characters: ` DS:60min`
+  return datastreamName.slice(0, -9);
+};
+
+/**
+ * Format the response containing a Datastream's metadata from Sensorthings API
+ * @param {Object} datastreamMetadata An object containing a Datastream's metadata
+ * @param {Boolean} isMetadataForAggregation A flag to determine if the datastream metadata will be used for aggregation. Set to `true` if metadata will be used for aggregation, `false` if not
+ * @returns {Object} An object containing the formatted metadata that is suitable for use in a chart
+ */
+const formatDatastreamMetadataForChart = function (
+  datastreamMetadata,
+  isMetadataForAggregation
+) {
+  if (
+    datastreamMetadata === undefined ||
+    isMetadataForAggregation === undefined
+  )
+    throw new Error(
+      "This function expects two arguments, ensure that both have been supplied"
+    );
+
+  const {
+    description: datastreamDescription,
+    name: datastreamName,
+    unitOfMeasurement,
+  } = datastreamMetadata;
+
+  // Extract phenomenon name from Datastream name
+  const phenomenonName =
+    extractPhenomenonNameFromDatastreamName(datastreamName);
+
+  // Extract building ID + phenomenon name from Datastream name
+  const buildingIdPhenomenonName =
+    extractBuildingIdPhenomenonNameFromDatastreamName(datastreamName);
+
+  // Get the unitOfMeasurement's symbol
+  const unitOfMeasurementSymbol = matchUnitOfMeasurementSymbolStringToSymbol(
+    unitOfMeasurement.symbol
+  );
+
+  // Case 1: Metadata NOT USED for aggregation; "isMetadataForAggregation" = false
+  if (!isMetadataForAggregation)
+    return {
+      datastreamDescription,
+      datastreamName,
+      phenomenonName,
+      unitOfMeasurementSymbol,
+    };
+
+  // Case 2: Metadata USED for aggregation; "isMetadataForAggregation" = true
+  return {
+    datastreamDescription,
+    datastreamName,
+    buildingIdPhenomenonName,
+    unitOfMeasurementSymbol,
+  };
+};
+
+/**
+ * Extract the properties that make up the formatted datastream metadata object(s)
+ * @param {Array} formattedDatastreamsMetadataArr An array of formatted metadata object(s) from one or more datastreams
+ * @param {Boolean} isMetadataForAggregation A flag to determine if the datastream metadata will be used for aggregation. Set to `true` if metadata will be used for aggregation, `false` if not
+ * @param {String} [aggregationInterval] The aggregation interval as a string, either "daily" or "monthly". Required when `isMetadataForAggregation = true`
+ * @param {String} [aggregationType] The aggregation type as a string, either "sum" or "average". Required when `isMetadataForAggregation = true`
+ * @returns {Object} An object that contains array(s) of formatted datastream metadata properties
+ */
+const extractPropertiesFromFormattedDatastreamMetadata = function (
+  formattedDatastreamsMetadataArr,
+  isMetadataForAggregation,
+  aggregationInterval,
+  aggregationType
+) {
+  if (
+    formattedDatastreamsMetadataArr === undefined ||
+    isMetadataForAggregation === undefined
+  )
+    throw new Error(
+      "This function expects two arguments, ensure that both have been supplied"
+    );
+
+  if (
+    formattedDatastreamsMetadataArr &&
+    isMetadataForAggregation &&
+    (aggregationInterval === undefined || aggregationType === undefined)
+  )
+    throw new Error(
+      "This function expects four arguments, ensure that all of them have been supplied"
+    );
+
+  if (
+    isMetadataForAggregation &&
+    aggregationInterval !== "daily" &&
+    aggregationInterval !== "monthly"
+  )
+    throw new Error(
+      `The supported aggegation interval strings are "daily" or "monthly"`
+    );
+
+  if (
+    isMetadataForAggregation &&
+    aggregationType !== "sum" &&
+    aggregationType !== "average"
+  )
+    throw new Error(
+      `The supported aggegation type strings are "sum" or "average"`
+    );
+
+  // Create arrays from the properties of the formatted datastream metadata
+  const datastreamDescriptionsArr = formattedDatastreamsMetadataArr.map(
+    (datastreamMetadata) => datastreamMetadata.datastreamDescription
+  );
+
+  const datastreamNamesArr = formattedDatastreamsMetadataArr.map(
+    (datastreamMetadata) => datastreamMetadata.datastreamName
+  );
+
+  const phenomenonNamesArr = formattedDatastreamsMetadataArr.map(
+    (datastreamMetadata) => datastreamMetadata.phenomenonName
+  );
+
+  const buildingIdsPhenomenonNamesArr = formattedDatastreamsMetadataArr.map(
+    (datastreamMetadata) => datastreamMetadata.buildingIdPhenomenonName
+  );
+
+  const unitOfMeasurementSymbolsArr = formattedDatastreamsMetadataArr.map(
+    (datastreamMetadata) => datastreamMetadata.unitOfMeasurementSymbol
+  );
+
+  // Case 1: Metadata NOT USED for aggregation; "isMetadataForAggregation" = false
+  if (!isMetadataForAggregation)
+    return {
+      datastreamDescriptionsArr,
+      datastreamNamesArr,
+      phenomenonNamesArr,
+      unitOfMeasurementSymbolsArr,
+    };
+
+  // Case 2: Metadata USED for aggregation; "isMetadataForAggregation" = true
+  return {
+    datastreamDescriptionsArr,
+    datastreamNamesArr,
+    buildingIdsPhenomenonNamesArr,
+    unitOfMeasurementSymbolsArr,
+    aggregationInterval,
+    aggregationType,
+  };
+};
+
+export {
+  extractPhenomenonNameFromDatastreamName,
+  formatDatastreamMetadataForChart,
+  extractPropertiesFromFormattedDatastreamMetadata,
+};