diff --git a/package.json b/package.json index 96e1bb09e..8fe8d9ccc 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "js-yaml": "^4.1.0", "moment": "^2.29.4", + "moment-range": "^4.0.2", "ts-command-line-args": "^2.5.1", "typescript": "^5.1.6", "zod": "^3.22.4" diff --git a/src/models/time-sync.ts b/src/models/time-sync.ts index 2fba1326c..512388b6b 100644 --- a/src/models/time-sync.ts +++ b/src/models/time-sync.ts @@ -1,4 +1,6 @@ import moment = require('moment'); +import {extendMoment} from 'moment-range'; +const momentRange = extendMoment(moment); import {STRINGS} from '../config'; @@ -6,7 +8,7 @@ import {ERRORS} from '../util/errors'; import {UnitsDealer} from '../util/units-dealer'; import {ModelParams, ModelPluginInterface} from '../types/model-interface'; -import {TimeNormalizerConfig} from '../types/time-sync'; +import {PaddingReceipt, TimeNormalizerConfig} from '../types/time-sync'; import {UnitsDealerUsage} from '../types/units-dealer'; import {UnitKeyName} from '../types/units'; @@ -110,7 +112,7 @@ export class TimeSyncModel implements ModelPluginInterface { } const method = dealer.askToGiveMethodFor(metric); - acc[method] = method === 'avg' || method === 'sum' ? 0 : input[metric]; + acc[metric] = method === 'avg' || method === 'sum' ? 0 : input[metric]; return acc; }, {} as ModelParams); @@ -133,6 +135,67 @@ export class TimeSyncModel implements ModelPluginInterface { } } + /** + * Checks if padding is needed either at start of the timeline or the end and returns status. + */ + private checkPadding(inputs: ModelParams[]): PaddingReceipt { + const startDiffInSeconds = + moment(inputs[0].timestamp).diff(moment(this.startTime)) / 1000; + + const lastInput = inputs[inputs.length - 1]; + const endDiffInSeconds = + moment(lastInput.timestamp) + .add(lastInput.duration, 'seconds') + .diff(moment(this.endTime)) / 1000; + + return { + start: startDiffInSeconds > 0, + end: endDiffInSeconds < 0, + }; + } + + /** + * Pads zeroish inputs from the beginning or at the end of the inputs if needed. + */ + private padInputs( + inputs: ModelParams[], + pad: PaddingReceipt, + dealer: UnitsDealerUsage + ): ModelParams[] { + const {start, end} = pad; + const paddedFromBeginning = []; + + if (start) { + const dateRange = momentRange.range( + moment(this.startTime), + moment(inputs[0].timestamp).subtract(1, 'second') + ); + + for (const second of dateRange.by('second')) { + paddedFromBeginning.push( + this.fillWithZeroishInput(inputs[0], second.valueOf(), dealer) + ); // check if converting to value of is needed + } + } + + const paddedArray = paddedFromBeginning.concat(inputs); + + if (end) { + const lastInput = inputs[inputs.length - 1]; + const dateRange = momentRange.range( + moment(lastInput.timestamp).add(lastInput.duration + 1, 'seconds'), + moment(this.endTime) + ); + + for (const second of dateRange.by('second')) { + paddedArray.push( + this.fillWithZeroishInput(lastInput, second.valueOf(), dealer) + ); + } + } + return paddedArray; + } + /** * Normalizes provided time window according to time configuration. */ @@ -140,8 +203,10 @@ export class TimeSyncModel implements ModelPluginInterface { this.validateParams(); const dealer = await UnitsDealer(); + const pad = this.checkPadding(inputs); + const paddedInputs = this.padInputs(inputs, pad, dealer); - return inputs + return paddedInputs .reduce((acc, input, index) => { const currentMoment = moment(input.timestamp); @@ -149,7 +214,7 @@ export class TimeSyncModel implements ModelPluginInterface { * Checks if not the first input, then check consistency with previous ones. */ if (index > 0) { - const previousInput = inputs[index - 1]; + const previousInput = paddedInputs[index - 1]; const previousInputTimestamp = moment(previousInput.timestamp); const compareableTime = previousInputTimestamp.add( previousInput.duration, diff --git a/src/types/time-sync.ts b/src/types/time-sync.ts index 9f7f10e87..fb1f8894a 100644 --- a/src/types/time-sync.ts +++ b/src/types/time-sync.ts @@ -3,3 +3,8 @@ export type TimeNormalizerConfig = { 'end-time': string; interval: number; }; + +export type PaddingReceipt = { + start: boolean; + end: boolean; +};