Last active
February 12, 2019 19:01
-
-
Save tigreped/8b0d2b472314e3ede0dd2e05f84d9bf9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 'deliveryStatisticsData': function (type, startPeriod, endPeriod, userId, metrics, metricsFriendlyName) { | |
| // Auxiliary variable to select the collection dynamically for the queries | |
| var databaseClass = null; | |
| // Set type according to the user input and provide appropriate databaseClass | |
| if (type === "Keyword") { | |
| databaseClass = StatsKeywordsAggregatedMinute; | |
| } | |
| if (type === "SERP") { | |
| databaseClass = StatsSerpsAggregatedMinute; | |
| } | |
| // Group by minute: | |
| var format = '%Y-%m-%dT%H:%M'; | |
| // The string field for the record's period | |
| var periodField = 'period'; | |
| // The field that is used as the average weight | |
| var weightField = 'generated'; | |
| // The filter field from metrics to be multiplied by the weight in the weighted average ($avgSolveTime, $callbacksExecuted, etc) | |
| var valueField = metrics; | |
| // The date field used to group data by minute | |
| var dateField = 'last'; | |
| // Fetch isoString from user input on datepicker | |
| var periodLowerRange = startPeriod.toISOString(); | |
| var periodUpperRange = endPeriod.toISOString(); | |
| // Receives the return of the cursor | |
| var queryCursor = null; | |
| // Raw collection object from Mongo | |
| var rawCollection = databaseClass.rawCollection(); | |
| // Check if it is a valid user for the match filter of the aggregation | |
| var validUser = (userId !== undefined && userId !== null && userId.length > 0); | |
| // When the value field is the avgSolveTime, calculate the weighted average | |
| if (valueField === 'avgSolveTime') { | |
| // Pipeline array with aggregation settings for the accumulators | |
| var pipeline = [ | |
| { | |
| // Match the values in the interest period range | |
| $match: { | |
| 'period': {'$gte': periodLowerRange, '$lte': periodUpperRange}, | |
| // If a single user is provided, pass it | |
| 'userId': (validUser ? userId : {}) | |
| } | |
| }, { | |
| // Group by $last time field as _id passing one minute format and | |
| // define numerator and denominator to calculate the weighted average | |
| $group: { | |
| _id: {$dateToString: {format: format, date: '$last'}}, | |
| numerator: {$sum: {$multiply: ['$' + [weightField], '$' + [valueField]]}}, | |
| denominator: {$sum: '$' + [weightField]} | |
| }, | |
| }, { | |
| // Output the minute as the _id and the weightedAverage value | |
| $project: { | |
| weightedAverage: {$divide: ["$numerator", "$denominator"]} | |
| } | |
| }, { | |
| // Sort data by time ascending | |
| $sort: { | |
| _id: 1 | |
| } | |
| } | |
| ]; | |
| } else { // Otherwise, just sum the values | |
| // Pipeline array with aggregation settings for the accumulators | |
| var pipeline = [ | |
| { | |
| // Match the values in the interest period range | |
| $match: { | |
| 'period': {'$gte': periodLowerRange, '$lte': periodUpperRange}, | |
| // If a single user is provided, pass it | |
| 'userId': (validUser ? userId : {}) | |
| } | |
| }, { | |
| // Group by $last time field as _id passing one minute format and | |
| // sum all values for the provided valueField | |
| $group: { | |
| _id: {$dateToString: {format: format, date: '$last'}}, | |
| total: { $sum: '$'+[valueField] } | |
| }, | |
| }, { | |
| // Output the minute as the _id and the weightedAverage value | |
| $project: { | |
| total: '$total' | |
| } | |
| }, { | |
| // Sort data by time ascending | |
| $sort: { | |
| _id: 1 | |
| } | |
| } | |
| ]; | |
| } | |
| console.log('* pipeline: ' + JSON.stringify(pipeline)); | |
| // Fetch data from database using Promise.await | |
| const items = Promise.await(rawCollection.aggregate(pipeline).toArray()); | |
| // Keep track of the total number of records returned | |
| var size = items.length; | |
| console.log('*** items.length: ' + size); | |
| // Map the data from the object field in the array element directly to the values in the chartDataSeries auxiliary array. | |
| //var chartDataSeries = items.map(x => Math.round( x[filterField])); | |
| // TODO: must treat the minutes with no data adding zero to these cases | |
| var chartDataSeries = items.map(x => x['weightedAverage']); | |
| console.log('*** chartDataSeries: ' + chartDataSeries); | |
| var max = Math.max(...chartDataSeries); | |
| console.log('*** min: ' + min); | |
| var min = Math.min(...chartDataSeries); | |
| console.log('*** max: ' + max); | |
| // Step #1 Calculate the mean by summing up all elements' values | |
| var total = 0; | |
| for(var i = 0 ; i < size ; i++) { | |
| if (chartDataSeries[i] !== null && chartDataSeries[i] !== undefined) { | |
| total = total + chartDataSeries[i]; | |
| } | |
| } | |
| // The mean is the total sum divided by the number of elements | |
| var mean = 0; | |
| if (size !== 0) { | |
| mean = total / size; | |
| } | |
| // Step #2 Calculate standard deviation. First, find the square of the sum of all subtractions between item value and the mean | |
| var sum = 0; | |
| for(var i = 0 ; i < size ; i++) { | |
| if (chartDataSeries[i] !== null && chartDataSeries[i] !== undefined) { | |
| sum = sum + Math.pow((chartDataSeries[i] - mean), 2); | |
| } | |
| } | |
| // The standard deviation is the squared root of the sum divided by the number of elements | |
| var stdDeviation = 0; | |
| if (size !== 0) { | |
| stdDeviation = Math.sqrt(sum/size); | |
| } | |
| // Step #3 Calculate the median, central values. | |
| var median = 0; | |
| // Sort data in ascending order, using another auxiliary array, to avoid sorting data for other calculations | |
| var medianArray = items.map(x => x['weightedAverage']).sort((a, b) => a - b); | |
| // Result items are sorted. Fetching the central values should suffice in order to find the median value(s) | |
| var medianIndex = Math.round(size/2); | |
| // Odd cases, fetch central value | |
| if (size%2 === 1) { | |
| if (medianArray[medianIndex] !== null && medianArray[medianIndex] !== undefined) { | |
| median = parseFloat(medianArray[medianIndex]); | |
| } | |
| } else { | |
| // Even cases, fetch the two central values and calculate their average | |
| if (medianArray[medianIndex] !== null && medianArray[medianIndex] !== undefined && | |
| medianArray[medianIndex-1] !== null && medianArray[medianIndex-1] !== undefined) { | |
| median = (parseFloat(medianArray[medianIndex - 1]) + parseFloat(medianArray[medianIndex])) / 2; | |
| } | |
| } | |
| // Mode - Separate values into bins and perform bin occurrence counts | |
| var bins = [{}]; | |
| for (var i = 0; i < size; i++) { | |
| // Ignore items where avgSolveTime is not valid | |
| if (chartDataSeries[i] !== null && chartDataSeries[i] !== undefined) { | |
| // Increment the index for the given bucket value | |
| var present = false; | |
| var roundAverage = Math.round(chartDataSeries[i]); | |
| // Check in the bins array | |
| bins.forEach(function(bin) { | |
| // Found one bin already present for the value, increment | |
| if (bin['value'] !== null && bin['value'] !== undefined && bin['value'] === roundAverage) { | |
| // It is already present | |
| present = true; | |
| // Update the 'times' field of the current bin with the value 1 | |
| // if empty for this value, or increment by one | |
| if (bin['times'] === null || bin['times'] === undefined) { | |
| bin['times'] = 1; | |
| } else { | |
| bin['times'] = bin['times'] + 1; | |
| } | |
| } | |
| }); | |
| // If the value was not yet present, add it to the bins: | |
| if (!present) { | |
| bins.push({ | |
| 'value': roundAverage, | |
| 'times': 1 | |
| }) | |
| } | |
| } | |
| } | |
| var mode = 0; | |
| var highestTimes = 0; | |
| // Fetch bin with the item that has the greatest value for the times field | |
| for (var i = 0 ; i < bins.length; i++) { | |
| if (bins[i] !== null && bins[i] !== undefined) { | |
| var bin = bins[i]; | |
| // log.push('{' + bin['value'] + ', ' + bin['times'] + '}'); | |
| if (bin.times > highestTimes) { | |
| // Updates the highest value from the bin times | |
| highestTimes = bin.times; | |
| // Records the index of the highest value | |
| mode = bin.value; | |
| } | |
| } | |
| } | |
| // Calculate the KPIS data based on average solve time | |
| var avgSolveTimeItems = databaseClass.find({ | |
| 'period': { '$gte': periodLowerRange, '$lte': periodUpperRange }, | |
| 'userId': ( validUser ? userId : {} ) | |
| },{ | |
| // Sort data by period to ensure time sequence to the plotted data | |
| sort: { 'avgSolveTime': 1 }, | |
| // Remove element _id and return only the value of the field of interest | |
| fields: { avgSolveTime: 1, _id: 0 } | |
| }).fetch(); | |
| var solveTimeArray = avgSolveTimeItems.map(x => x['avgSolveTime']); | |
| // Keep track of the total number of records returned | |
| var avgSolveTimeItemsSize = solveTimeArray.length; | |
| var solveTimeBins = { | |
| '10': 0, | |
| '60': 0, | |
| '300': 0, | |
| '1800': 0, | |
| '3600': 0, | |
| '86400': 0 | |
| }; | |
| for (var i = 0; i < avgSolveTimeItemsSize; i++) { | |
| // Ignore items where avgSolveTime is not valid | |
| if (solveTimeArray[i] !== null && solveTimeArray[i] !== undefined) { | |
| // Increment the categories | |
| var solveTimeItem = solveTimeArray[i]; | |
| // Less then 10 seconds: | |
| if (solveTimeItem <= 10) { | |
| solveTimeBins['10'] = solveTimeBins['10'] + 1; | |
| } | |
| // Less then 1 minute: | |
| if (solveTimeItem <= 60) { | |
| solveTimeBins['60'] = solveTimeBins['60'] + 1; | |
| } | |
| // Less then 5 minutes: | |
| if (solveTimeItem <= 60 * 5) { | |
| solveTimeBins['300'] = solveTimeBins['300'] + 1; | |
| } | |
| // Less then 30 minutes: | |
| if (solveTimeItem <= 60 * 30) { | |
| solveTimeBins['1800'] = solveTimeBins['1800'] + 1; | |
| } | |
| // Less then 1 hour: | |
| if (solveTimeItem <= 60 * 60) { | |
| solveTimeBins['3600'] = solveTimeBins['3600'] + 1; | |
| } | |
| // Less then 1 day: | |
| if (solveTimeItem <= 60 * 60 * 24) { | |
| solveTimeBins['86400'] = solveTimeBins['86400'] + 1; | |
| } | |
| } | |
| } | |
| // Calculate only the percentages and use parseFloat.toFiexd(2) to ensure displaying only two decimal digits | |
| var solveTimePercentage = {}; | |
| if (avgSolveTimeItemsSize !== 0) { | |
| solveTimePercentage['10'] = parseFloat((solveTimeBins['10'] * 100) / avgSolveTimeItemsSize).toFixed(2); | |
| solveTimePercentage['60'] = parseFloat((solveTimeBins['60'] * 100) / avgSolveTimeItemsSize).toFixed(2); | |
| solveTimePercentage['300'] = parseFloat((solveTimeBins['300'] * 100) / avgSolveTimeItemsSize).toFixed(2); | |
| solveTimePercentage['1800'] = parseFloat((solveTimeBins['1800'] * 100) / avgSolveTimeItemsSize).toFixed(2); | |
| solveTimePercentage['3600'] = parseFloat((solveTimeBins['3600'] * 100) / avgSolveTimeItemsSize).toFixed(2); | |
| solveTimePercentage['86400'] = parseFloat((solveTimeBins['86400'] * 100) / avgSolveTimeItemsSize).toFixed(2); | |
| } | |
| var data = { | |
| "chart": { | |
| "chartTitle": metricsFriendlyName, | |
| "seriesData": chartDataSeries | |
| }, | |
| "statisticsTable": [ | |
| {solveTime: "10 seconds", probability: solveTimePercentage['10']}, | |
| {solveTime: "1 minute", probability: solveTimePercentage['60']}, | |
| {solveTime: "5 minutes", probability: solveTimePercentage['300']}, | |
| {solveTime: "30 minutes", probability: solveTimePercentage['1800']}, | |
| {solveTime: "1 hour", probability: solveTimePercentage['3600']}, | |
| {solveTime: "24 hours", probability: solveTimePercentage['86400']} | |
| ], | |
| "kpisTables": { | |
| total: parseFloat(size).toFixed(2), | |
| median: parseFloat(median).toFixed(2), | |
| mode: parseFloat(mode).toFixed(2), | |
| mean: parseFloat(mean).toFixed(2), | |
| min: parseFloat(min).toFixed(2), | |
| max: parseFloat(max).toFixed(2), | |
| stdDeviation: parseFloat(stdDeviation).toFixed(2) | |
| } | |
| }; | |
| return data; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment