All files / if-run/lib aggregate.ts

100% Statements 62/62
100% Branches 18/18
100% Functions 10/10
100% Lines 58/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 1658x     8x 8x             8x 8x   8x             8x         8x 4x   4x 8x   8x               8x 2x 2x   2x 4x 4x     2x                           8x 8x 8x   8x 3x 5x   5x       8x   5x 3x       3x 2x 2x 2x                 8x 5x   5x 2x     3x 3x   3x           8x     793x 18x           793x           8x     8x   811x     18x       8x           8x 775x 775x 775x 775x   775x       201x     574x   574x          
import {AGGREGATION_METHODS} from '@grnsft/if-core/consts';
import {PluginParams} from '@grnsft/if-core/types';
 
import {debugLogger} from '../../common/util/debug-logger';
import {logger} from '../../common/util/logger';
import {
  AggregationParams,
  AggregationParamsSure,
  AggregationMetricsWithMethod,
} from '../../common/types/manifest';
 
import {aggregateOutputsIntoOne} from '../util/aggregation-helper';
import {memoizedLog} from '../util/log-memoize';
 
import {STRINGS} from '../config/strings';
 
const {
  AGGREGATING_NODE,
  AGGREGATING_OUTPUTS,
  UNKNOWN_PARAM,
  CHECKING_AGGREGATION_METHOD,
} = STRINGS;
 
/**
 * Gets `i`th element from all children outputs and collects them in single array.
 */
const getIthElementsFromChildren = (children: any, i: number) => {
  const values = Object.values(children);
 
  return values.map((value: any) => {
    const output = value.outputs;
 
    return output[i];
  });
};
 
/**
 * 1. Gets the i'th element from each childrens outputs (treating children as rows and we are after a column of data).
 * 2. Now we just aggregate over the `ithSliceOfOutputs` the same as we did for the normal outputs.
 */
const temporalAggregation = (node: any, metrics: string[]) => {
  const outputs: PluginParams[] = [];
  const values: any = Object.values(node.children);
 
  for (let i = 0; i < values[0].outputs.length; i++) {
    const ithSliceOfOutputs = getIthElementsFromChildren(node.children, i);
    outputs.push(aggregateOutputsIntoOne(ithSliceOfOutputs, metrics, true));
  }
 
  return outputs;
};
 
/**
 * Navigates the tree depth first, bottom up,
 *  left to right aggregating the component nodes and then the grouping nodes will be aggregated
 *  only when all their child nodes have been aggregated.
 * 1. Aggregates all the children.
 * 2. At this point you can be positive all your children have been aggregated and so you can now work on aggregating yourself.
 * 3. It's component node, аggregates just the outputs of THIS component node (horizontal/component aggregation).
 * 4. Else it's grouping node, first does temporal aggregation. This assumes everything is on the same time-grid.
 *    The outputs of the grouping node are the aggregated time bucketed outputs of it's children.
 * 5. Now a grouping node has it's own outputs, it can horizotnally aggregate them.
 */
const aggregateNode = (node: any, aggregationParams: AggregationParamsSure) => {
  const metrics = aggregationParams!.metrics;
  const type = aggregationParams!.type;
 
  if (node.children) {
    for (const child in node.children) {
      console.debug(AGGREGATING_NODE(child));
 
      aggregateNode(node.children[child], aggregationParams);
    }
  }
 
  if (!node.children) {
    /** `time` aggregation is the new name of `horizontal`. */
    if (type === 'horizontal' || type === 'time' || type === 'both') {
      node.aggregated = aggregateOutputsIntoOne(node.outputs, metrics);
    }
  } else {
    /** `component` aggregation is the new name of `vertical`. */
    if (type === 'vertical' || type === 'component' || type === 'both') {
      const outputs = temporalAggregation(node, metrics);
      node.outputs = outputs;
      node.aggregated = aggregateOutputsIntoOne(outputs, metrics);
    }
  }
};
 
/**
 * If aggregation is disabled, then returns given `tree`.
 * Otherwise creates copy of the tree, then applies aggregation to it.
 */
export const aggregate = (tree: any, aggregationParams: AggregationParams) => {
  console.debug(AGGREGATING_OUTPUTS);
 
  if (!aggregationParams || !aggregationParams.type) {
    return tree;
  }
 
  const copyOfTree = structuredClone(tree);
  aggregateNode(copyOfTree, aggregationParams);
 
  return copyOfTree;
};
 
/**
 * Gets or stores aggregation metrics.
 */
export const storeAggregationMetrics = (
  aggregationMetrics?: AggregationMetricsWithMethod
) => {
  if (aggregationMetrics) {
    metricManager.metrics = {
      ...metricManager.metrics,
      ...aggregationMetrics,
    };
  }
 
  return metricManager.metrics;
};
 
/**
 * Creates an encapsulated object to retrieve the metrics.
 */
const metricManager = (() => {
  let metric: AggregationMetricsWithMethod;
 
  const manager = {
    get metrics() {
      return metric;
    },
    set metrics(value: AggregationMetricsWithMethod) {
      metric = value;
    },
  };
 
  return manager;
})();
 
/**
 * Returns aggregation method for given `metric`.
 */
export const getAggregationInfoFor = (metric: string) => {
  debugLogger.setExecutingPluginName();
  memoizedLog(console.debug, '\n');
  memoizedLog(console.debug, CHECKING_AGGREGATION_METHOD(metric));
  const aggregationMetricsStorage = storeAggregationMetrics();
 
  if (
    aggregationMetricsStorage &&
    Object.keys(aggregationMetricsStorage).includes(metric)
  ) {
    return aggregationMetricsStorage[metric];
  }
 
  memoizedLog(logger.warn, UNKNOWN_PARAM(metric));
 
  return {
    time: AGGREGATION_METHODS[3],
    component: AGGREGATION_METHODS[3],
  };
};