All files / if-run/builtins/mock-observations index.ts

100% Statements 58/58
92.3% Branches 12/13
100% Functions 16/16
100% Lines 56/56

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 165 166 167 168 169 170 171 172 173 174 1755x 5x   5x           5x   5x   5x   5x 5x       5x 5x   5x   11x 1x     10x                     10x       3x 2x   2x 4x     2x   2x 4x 4x 8x         8x     4x               5x             3x   3x     3x   3x                             5x           9x       6x             3x           5x 3x       3x 3x 3x     3x 6x                 5x       8x 8x   8x 16x 16x   16x   16x     16x 8x 8x         8x 16x       8x    
import {DateTime, Duration} from 'luxon';
import {z} from 'zod';
 
import {PluginFactory} from '@grnsft/if-core/interfaces';
import {
  PluginParams,
  ConfigParams,
  ObservationParams,
} from '@grnsft/if-core/types';
import {ERRORS} from '@grnsft/if-core/utils';
 
import {validate} from '../../../common/util/validations';
 
import {STRINGS} from '../../config';
 
import {CommonGenerator} from './helpers/common-generator';
import {RandIntGenerator} from './helpers/rand-int-generator';
 
import {Generator} from './interfaces/index';
 
const {ConfigError} = ERRORS;
const {MISSING_CONFIG} = STRINGS;
 
export const MockObservations = PluginFactory({
  configValidation: (config: ConfigParams) => {
    if (!config || !Object.keys(config)?.length) {
      throw new ConfigError(MISSING_CONFIG);
    }
 
    const schema = z.object({
      'timestamp-from': z.string(),
      'timestamp-to': z.string(),
      duration: z.number().gt(0),
      components: z.array(z.record(z.string())),
      generators: z.object({
        common: z.record(z.string().or(z.number())),
        randint: z.record(z.object({min: z.number(), max: z.number()})),
      }),
    });
 
    return validate<z.infer<typeof schema>>(schema, config);
  },
  implementation: async (inputs: PluginParams[], config: ConfigParams) => {
    const {duration, timeBuckets, components, generators} =
      generateParamsFromConfig(config);
    const generatorToHistory = new Map<Generator, number[]>();
 
    generators.forEach(generator => {
      generatorToHistory.set(generator, []);
    });
 
    const defaults = inputs && inputs[0];
 
    return Object.entries(components).reduce((acc: PluginParams[], item) => {
      const component: any = item[1];
      timeBuckets.forEach(timeBucket => {
        const observation = createObservation(
          {duration, component, timeBucket, generators},
          generatorToHistory
        );
 
        acc.push(Object.assign({}, defaults, observation));
      });
 
      return acc;
    }, []);
  },
});
 
/**
 * Configures the MockObservations Plugin for IF
 */
const generateParamsFromConfig = (config: ConfigParams) => {
  const {
    'timestamp-from': timestampFrom,
    'timestamp-to': timestampTo,
    duration,
    generators,
    components,
  } = config;
 
  const convertedTimestampFrom = DateTime.fromISO(timestampFrom, {
    zone: 'UTC',
  });
  const convertedTimestampTo = DateTime.fromISO(timestampTo, {zone: 'UTC'});
 
  return {
    duration,
    timeBuckets: createTimeBuckets(
      convertedTimestampFrom,
      convertedTimestampTo,
      duration
    ),
    generators: createGenerators(generators),
    components,
  };
};
 
/*
 * Creates time buckets based on start time, end time and duration of each bucket.
 */
const createTimeBuckets = (
  timestampFrom: DateTime,
  timestampTo: DateTime,
  duration: number,
  timeBuckets: DateTime[] = []
): DateTime[] => {
  if (
    timestampFrom < timestampTo ||
    timestampFrom.plus(Duration.fromObject({seconds: duration})) < timestampTo
  ) {
    return createTimeBuckets(
      timestampFrom.plus(Duration.fromObject({seconds: duration})),
      timestampTo,
      duration,
      [...timeBuckets, timestampFrom]
    );
  }
  return timeBuckets;
};
 
/*
 * Creates generators based on a given config
 */
const createGenerators = (generatorsConfig: object): Generator[] => {
  const createCommonGenerator = (config: any): Generator[] => [
    CommonGenerator(config),
  ];
 
  const createRandIntGenerators = (config: any): Generator[] =>
    Object.entries(config).map(([fieldToPopulate, value]) =>
      RandIntGenerator(fieldToPopulate, value as Record<string, any>)
    );
 
  return Object.entries(generatorsConfig).flatMap(([key, value]) =>
    key === 'randint'
      ? createRandIntGenerators(value).flat()
      : createCommonGenerator(value)
  );
};
 
/*
 * Creates time buckets based on start time, end time and duration of each bucket.
 */
const createObservation = (
  observationParams: ObservationParams,
  generatorToHistory: Map<Generator, number[]>
): PluginParams => {
  const {duration, component, timeBucket, generators} = observationParams;
  const timestamp = timeBucket.toISO();
 
  const generateObservation = (generator: Generator) => {
    const history = generatorToHistory.get(generator) || [];
    const generated: Record<string, any> = generator.next(history);
 
    generatorToHistory.set(generator, [...history, generated.value]);
 
    return generated;
  };
 
  const generateObservations = (gen: Generator) => generateObservation(gen);
  const generatedValues = generators.map(generateObservations);
  const initialObservation: PluginParams = {
    timestamp,
    duration,
    ...component,
  };
  const generatedObservation = generatedValues.reduce(
    (observation, generated) => Object.assign(observation, generated),
    initialObservation
  );
 
  return generatedObservation as PluginParams;
};