Name

global.AvailabilityOutageProcessor

Description

No description available

Script

var AvailabilityOutageProcessor = Class.create();
AvailabilityOutageProcessor.prototype = {
  initialize: function() {
  	this.OUTAGE_TYPES = (new global.AvailabilityConstants()).OUTAGE_TYPES;
  },

  /**
   * Trims outages to specified calculation boundaries
   * @param {Object} outage Outage object to process
   * @param {Number} beginBoundary Begin cutoff time to check
   * @param {Number} endBoundary End cutoff time to check
   * @returns {Object|null} Returns object if duration of outage > 0 else returns null
   */
  _modifyOutageBoundaries: function(outage, beginBoundary, endBoundary) {
  	// Null checks
  	if (beginBoundary == null || endBoundary == null || !outage)
  		return null;

  	// Trim outage if outage begin is less than begin boundary
  	if (outage.begin < beginBoundary)
  		outage.begin = beginBoundary;

  	// If outage end is set then it is not an outgoing outage
  	// Trim outage end if outage end  is bigger than end boundary
  	if (outage.end > endBoundary)
  		outage.end = endBoundary;

  	// Check for outage duration
  	// Duration will be 0 in the following case:
  	// Outage begin is the same as begin boundary since we've adjusted boundaries already
  	// Since outages are ordered by begin date we can safely assume that ALL
  	// of the remaining outages in the array are not to be considered in the calculation
  	// bceause they do not meet the period cutoff (begin and end boundaries)
  	if (outage.end - outage.begin === 0)
  		return null;

  	// Return outage with modified boundaries
  	return outage;
  },

  /**
   * Inserts outage at given position
   * @param {Array} outages
   * @param {Number} position
   * @param {Object} newOutage
   */
  _insertOutage: function(outages, position, newOutage) {
  	if (!outages || position === null || position === undefined || !newOutage)
  		return;
  	outages.splice(position, 0, newOutage);
  },

  /**
   * Deletes outage at given position
   * @param {Array} outages Outages array (reference)
   * @param {Number} position
   */
  _deleteOutage: function(outages, position) {
  	if (!outages || position === null || position === undefined)
  		return;
  	outages.splice(position, 1);
  },

  /**
   * Finds and merges outages within the range of the first outages
   * @param {Array} outages
   * @param {Number} currentPositionInArray
   */
  _mergeLongestPossiblePlannedOutage: function(outages, currentPositionInArray) {
  	var currentOutage = outages[currentPositionInArray];
  	// Build longest possible planned outage within current plannedOutage range
  	for (var j = currentPositionInArray + 1; j < outages.length; j++) {
  		var nextOutage = outages[j];
  		if (!nextOutage)
  			break;
  		// Merge planned outage if it is in range
  		else if (nextOutage.type == this.OUTAGE_TYPES.PLANNED && nextOutage.begin <= currentOutage.end) {
  			outages[currentPositionInArray].begin = Math.min(currentOutage.begin, nextOutage.begin);
  			outages[currentPositionInArray].end = Math.max(currentOutage.end, nextOutage.end);
  			this._deleteOutage(outages, j);
  			// Since we deleted an outage, array shifts to the right by 1
  			// Make sure we check the outage that shifted to the right
  			j--;
  		} else if (nextOutage.begin > currentOutage.end)
  			break;
  	}
  },

  /**
   * Function that finds the index in the outage array where the given outage would be in order
   * @param {Array} outages
   * @param {Object} newOutage outage to insert
   * @param {Number} startingPosition current position in array
   * @returns {Number} position where element should be inserted
   */
  _seekNewIndexInArray: function(outages, newOutage, startingPosition) {
  	var currentPosition = startingPosition;
  	var currentOutage = outages[currentPosition];
  	while (currentOutage != null && newOutage.begin > outages[currentPosition].begin) {
  		currentPosition++;
  		currentOutage = outages[currentPosition];
  	}
  	return currentPosition;
  },

  /**
   * Determines index adjustment for re-check depending on if we're scanning
   * the array to the left or right
   * @param {String} direction either 'left' or 'right'
   * @param {Number} currentIndex current position in the array
   * @returns {Number} containing the index offset
   */
  _determineIndexAdjustment: function(direction, currentIndex) {
  	if (direction === 'left')
  		return currentIndex + 1;
  	else if (direction === 'right')
  		return currentIndex - 1;
  },

  /**
   * Checks if two different types of outages (one planned and one outage) overlap with each other
   * If they do, then it trims/splits/deletes outageOutage accordingly
   * @param {Array} outages
   * @param {Object} indexes object with the following properties:
   * {currentIndex, plannedOutageIndex, outageOutageIndex}
   * @param {String} direction direction in the array we're checking
   * @returns {Number}
   */
  _checkPlannedOutageOverlap: function(outages, indexes, direction) {
  	// Outages array is in order. Current possible cases are:
  	// - CASE 1: The plannedOutage is in the middle of an outageOutage. Split outage
  	//   (outageOutage.begin < plannedOutage.begin && outageOutage.end > plannedOutage.end)
  	// - CASE 2: The outageOutage is completely within the planned outage. Delete outage
  	//   (outageOutage.begin >= plannedOutage.begin && outageOutage.end <= plannedOutage.end)
  	// - CASE 3: The outageOutage end goes past plannedOutage end date. We need to modify begin of outageOutage
  	// - CASE 4: The outageOutage begin is before plannedOutage begin date. We need to modify end of outageOutage

  	if (!indexes || indexes.currentIndex == null || indexes.plannedOutageIndex == null || indexes.outageOutageIndex == null)
  		return;
  	var currentIndex = indexes.currentIndex;
  	var previousOrNextIndex = direction === 'left' ? currentIndex - 1 : currentIndex + 1;
  	var plannedOutageIndex = indexes.plannedOutageIndex;
  	var outageOutageIndex = indexes.outageOutageIndex;
  	var plannedOutage = outages[plannedOutageIndex];
  	var outageOutage = outages[outageOutageIndex];
  	if (!plannedOutage || !outageOutage)
  		return currentIndex;

  	// - CASE 1: The plannedOutage is in the middle of an outageOutage. Split outage
  	if (outageOutage.begin < plannedOutage.begin && outageOutage.end > plannedOutage.end) {
  		// Planned outage is in the middle, split outageOutage
  		// Create new upper outage and insert it after planned outage
  		var upperOutage = {begin: plannedOutage.end, end: outageOutage.end, type: this.OUTAGE_TYPES.OUTAGE};

  		// Modify outageOutage to be the lower outage
  		outages[outageOutageIndex].end = plannedOutage.begin;
  		var newIndexInOutageArray = this._seekNewIndexInArray(outages, upperOutage, previousOrNextIndex);
  		this._insertOutage(outages, newIndexInOutageArray, upperOutage);
  		// No need to adjust index since no deletion happened

  	// - CASE 2: The outageOutage is completely within the planned outage. Delete outage
  	} else if ((outageOutage.begin >= plannedOutage.begin && outageOutage.end <= plannedOutage.end)) {
  		this._deleteOutage(outages, currentIndex);
  		return this._determineIndexAdjustment(direction, currentIndex);

  	// - CASE 3: The outageOutage end goes past plannedOutage end date. We need to modify begin of outageOutage
  	} else if (outageOutage.begin >= plannedOutage.begin && outageOutage.end > plannedOutage.end) {
  		// Adjust begin date
  		outages[outageOutageIndex].begin = plannedOutage.end;
  		// At this point array is not in order since there can be outages within our planned outage
  		// that have not been processed yet. Look for new position in array and then insert there
  		var newPositionInArray = this._seekNewIndexInArray(outages, outageOutage, previousOrNextIndex);
  		this._insertOutage(outages, newPositionInArray, outages[currentIndex]);
  		// Delete outage after insertion
  		this._deleteOutage(outages, currentIndex);
  		return this._determineIndexAdjustment(direction, currentIndex);

  	// - CASE 4: The outageOutage begin is before plannedOutage begin date. We need to modify end of outageOutage
  	} else if (outageOutage.begin < plannedOutage.begin && outageOutage.end <= plannedOutage.end)
  		// Adjust end date
  		outages[outageOutageIndex].end = plannedOutage.begin;

  	return currentIndex;
  },

  /**
   * Processes outages and finds overlaps among outage objects
   * If outages are of the same type and there is an overlap, the function merges them
   * If outages are of different type and there is an overap, the function trims/splits/deletes outageOutage accordingly
   * @param {Array} outages contains outages to process
   * @param {Number} beginBoundary UNIX Timestamp in millis containing the lower boundary for outages to process
   * @param {Number} endBoundary UNIX Timestamp in millis containing the higher boundary for outages to process
   * @returns {Array} of processed outages that contains no overlaps
   */
  processOutages: function(outages, beginBoundary, endBoundary) {
  	// If outages is empty
  	if (outages.length === 0)
  		return outages;
  	// If outages has only one outage, return dataset as is with modified boundaries
  	else if (outages.length === 1) {
  		// If outage is planned then return empty array since we don't need planned outages
  		if (outages[0].type == this.OUTAGE_TYPES.PLANNED)
  			return [];
  		var modifiedOutage = this._modifyOutageBoundaries(outages[0], beginBoundary, endBoundary);
  		// If modified outage is not within boundaries return empty dataset
  		if (!modifiedOutage)
  			return [];
  		// Return outage with modified boundaries
  		return [modifiedOutage];
  	}

  	for (var i = 0; i < outages.length; i++) {
  		var currentOutage = this._modifyOutageBoundaries(outages[i], beginBoundary, endBoundary);
  		// Check currentOutage is within boundaries
  		if (!currentOutage) {
  			this._deleteOutage(outages, i);
  			// We have reached end of the boundary for current calculation period
  			return;
  		}

  		if (currentOutage.type == this.OUTAGE_TYPES.PLANNED) {
  			this._mergeLongestPossiblePlannedOutage(outages, i);

  			var nextOutageIndex = i + 1;
  			var nextOutage;
  			// Check we're not at the end of the array for next Outage
  			if (nextOutageIndex > outages.length) {
  				// Delete outage if last is planned outage
  				this._deleteOutage(outages, i);
  				i--;
  				break;
  			}
  			// Since we checked for overlapping planned outages already, next outage in range can only be of type outage
  			nextOutage = outages[nextOutageIndex];

  			// Check for potential outages that need to be trimmed to the left of our planned outage
  			// At this point there shouldn't be any planned outages to the left of the array
  			for (var j = i - 1; j >= 0; j--) {
  				var previousOutage = outages[j];
  				var previousOutageIndex = j;
  				// Ignore other types
  				// CANT HAPPEN SINCE WE ALWAYS MAKE SURE THERE ARE NO PLANNED OUTAGES TO THE LEFT OF THE ARRAY
  				// WHEN PROCESSING
  				if (!previousOutage.type == this.OUTAGE_TYPES.OUTAGE)
  					continue;

  				var isPreviousOutageInRange = (previousOutage.end > currentOutage.begin);
  				if (isPreviousOutageInRange) {
  					var indexes = {currentIndex: i, plannedOutageIndex: i, outageOutageIndex: previousOutageIndex};
  					var indexOffset = this._checkPlannedOutageOverlap(outages, indexes, 'left');
  					j = indexOffset;
  				}
  				if (previousOutage.end <= currentOutage.begin)
  					break;
  			}

  			// NOTE: At this point, planned outage cannot be in the middle since planned outage is currentOutage
  			// outageOutages can be:
  			// FIRST CASE
  			// - Fully contained within, which means delete:
  			//	(outageOutage.begin >= plannedOutage.begin && outageOutage.end <= plannedOutage.end)
  			// SECOND CASE
  			// - Contained partially, which means trim (trim begin to planned outage end)
  			//  (outageOutage.begin >= plannedOutage.begin && outageOutage.end > plannedOutage.end)

  			// First check ensure we only process outages within the planned outage range
  			// Second check ensures we do not go out of array boundary
  			if (nextOutage.type != this.OUTAGE_TYPES.OUTAGE) {
  				// We can safely delete planned outage since we checked for planned outages in range and merged them
  				// At this point nextOutage should be a planned outage not in range
  				this._deleteOutage(outages, i);
  				i--;
  				continue;
  			}
  			var indexes = {currentIndex: i, plannedOutageIndex: i, outageOutageIndex: nextOutageIndex};
  			while ((nextOutage.begin < currentOutage.end) && (nextOutageIndex < outages.length)) {
  				// Check for delete (FIRST CASE)
  				if (nextOutage.begin >= currentOutage.begin && nextOutage.end <= currentOutage.end)
  					// No need to adjust array pointer i since currentOutage in next iteration will be
  					// whatever next outage there is to process
  					this._deleteOutage(outages, nextOutageIndex);

  					// Check for trim (SECOND CASE)
  				else if (nextOutage.begin >= currentOutage.begin && nextOutage.end > currentOutage.end) {
  					outages[nextOutageIndex].begin = currentOutage.end;
  					// Check array is still in order
  					var outageAfterNextIndex = nextOutageIndex + 1;
  					if (outageAfterNextIndex < outages.length) {
  						var newIndex = this._seekNewIndexInArray(outages, outages[nextOutageIndex], nextOutageIndex + 1);
  						// If new index is bigger than current index, we need to move outage
  						if (newIndex > nextOutageIndex + 1) {
  							this._insertOutage(outages, newIndex, outages[nextOutageIndex]);
  							// Delete shifts outage. Next iteration will be in desired position
  							this._deleteOutage(outages, nextOutageIndex);
  						} else
  							// Update nextOutageIndex manually
  							nextOutageIndex++;
  					}
  				}
  				// Update local reference to nextOutage
  				nextOutage = outages[nextOutageIndex];
  			}

  			// We have processed all the planned outages that needed to be merged with our planned outage
  			// and all the outage outages that were in range of the current planned outage that were either
  			// to the left or right of the array, we can safely remove planned outage from the array
  			this._deleteOutage(outages, i);
  			// Since we deleted currentOutage, elements in array shifted position by 1 to the left
  			// Adjust current array pointer to check for next currentOutage
  			i--;

  		// Else currentOutage is outage of type outage
  		} else {
  			var nextOutageIndex = i + 1;
  			var nextOutage, nextOutageType, nextOutageBeginDateIsInRange;
  			// Check we're not at the end of the array for next Outage
  			if (nextOutageIndex > outages.length)
  				break;

  			nextOutage = outages[nextOutageIndex];
  			nextOutageType = nextOutage.type;
  			nextOutageBeginDateIsInRange = (nextOutage.begin >= currentOutage.begin && nextOutage.begin <= currentOutage.end);

  			// Check next outage type
  			if (nextOutageType == this.OUTAGE_TYPES.OUTAGE) {
  				// Check if there is an overlap
  				if (nextOutageBeginDateIsInRange) {
  					// Modify end date to have the later date
  					outages[i].end = Math.max(currentOutage.end, nextOutage.end);
  					// Delete nextOutage since we've combined outages
  					this._deleteOutage(outages, nextOutageIndex);
  					i--;
  				}
  			// nextOutage is a planned outage
  			} else if (nextOutageType == this.OUTAGE_TYPES.PLANNED) {
  				// Check if there is an overlap
  				if (nextOutageBeginDateIsInRange) {
  					var indexes = {currentIndex: i, plannedOutageIndex: nextOutageIndex, outageOutageIndex: i};
  					var indexOffset = this._checkPlannedOutageOverlap(outages, indexes, 'right');
  					i = indexOffset;
  				}
  			}
  		}
  	}
  	return outages;
  },
  type: 'AvailabilityOutageProcessor'
};

Sys ID

44ee99055301211054f0ddeeff7b12b5

Offical Documentation

Official Docs: