import { DateTime } from 'luxon' import { Client } from 'cozy-client' import { IConsumptionDataManager, ITimePeriod, TimeStep, IDataload, IChartData, IPerformanceIndicator, } from './dataConsumptionContracts' import { FluidType } from 'enum/fluid.enum' import ConsumptionDataFormatter from './consumptionDataFormatterService' import { QueryRunner } from './queryRunnerService' import ConsumptionDataValidator from './consumptionDataValidatorService' import LoadToCurrencyConverter from './loadToCurrencyConverterService' // eslint-disable-next-line @typescript-eslint/interface-name-prefix export interface ISingleFluidChartData { chartData: IChartData | null chartFluid: FluidType } export default class ConsumptionDataManager implements IConsumptionDataManager { private readonly _client: Client private readonly _consumptionDataFormatter: ConsumptionDataFormatter private readonly _queryRunner: QueryRunner private readonly _consumptionDataValidator: ConsumptionDataValidator constructor(_client: Client) { this._client = _client this._consumptionDataFormatter = new ConsumptionDataFormatter() this._queryRunner = new QueryRunner(this._client) this._consumptionDataValidator = new ConsumptionDataValidator() } public async getGraphData( timePeriod: ITimePeriod, timeStep: TimeStep, fluidTypes: FluidType[], compareTimePeriod?: ITimePeriod ): Promise<IChartData | null> { const InputisValid: boolean = this._consumptionDataValidator.ValidateGetGraphData( timePeriod, timeStep, fluidTypes, compareTimePeriod ) if (!InputisValid) return null let mappedData = null if (fluidTypes.length === 1) { //TODO validating input data //TODO applying buisness logic to the query arguments // running the query const fetchedData = await this.fetchSingleFLuidGraphData( timePeriod, timeStep, fluidTypes[0], compareTimePeriod ) // formatting data const formattedData = this.formatGraphDataManage( fetchedData, timeStep, timePeriod, compareTimePeriod || null ) // validating output data // mapping result to contract mappedData = formattedData } else if (fluidTypes.length > 1) { const toBeAgreggatedData: ISingleFluidChartData[] = [] for (const fluidType of fluidTypes) { const fetchedData = await this.fetchSingleFLuidGraphData( timePeriod, timeStep, fluidType, compareTimePeriod ) // formatting data const formattedData = this.formatGraphDataManage( fetchedData, timeStep, timePeriod, compareTimePeriod || null ) // validating output data toBeAgreggatedData.push({ chartData: formattedData, chartFluid: fluidType, }) } const aggregatedData = this.aggregateGraphData(toBeAgreggatedData) // mapping result to contract mappedData = aggregatedData return mappedData } else return null return mappedData } public async getPerformanceIndicators( timePeriod: ITimePeriod, timeStep: TimeStep, fluidTypes: FluidType[], compareTimePeriod?: ITimePeriod ): Promise<IPerformanceIndicator[]> { //const result = {}; const performanceIndicators: IPerformanceIndicator[] = [] for (const fluideType of fluidTypes) { const graphData: IChartData | null = await this.getGraphData( timePeriod, timeStep, [fluideType], compareTimePeriod ) if (graphData) { const performanceIndicator: IPerformanceIndicator = { value: null, compareValue: null, percentageVariation: null, } const actualDataIsValid = this.PerformanceIndicatorsDataIsValid( graphData.actualData ) if (actualDataIsValid) performanceIndicator.value = this.calculatePerformanceIndicatorValue( graphData.actualData ) if ( actualDataIsValid && graphData.comparisonData && this.PerformanceIndicatorsDataIsValid(graphData.comparisonData) ) { const comparisonSumValue = this.calculatePerformanceIndicatorValue( graphData.comparisonData ) performanceIndicator.compareValue = comparisonSumValue performanceIndicator.percentageVariation = this.calculatePerformanceIndicatorVariationPercentage( performanceIndicator.value || 0, comparisonSumValue ) } performanceIndicators[fluideType] = performanceIndicator } } return performanceIndicators } private PerformanceIndicatorsDataIsValid(data: IDataload[]): boolean { if (!data) return false const missingValue = data.find(element => element.value === -1) if (missingValue) return false return true } private calculatePerformanceIndicatorValue(data: IDataload[]): number { return data.reduce((a, b) => a + b.value, 0) } private calculatePerformanceIndicatorVariationPercentage( dataSum: number, comparisonDataSum: number ): number { return 1 - dataSum / comparisonDataSum } private async fetchSingleFLuidGraphData( timePeriod: ITimePeriod, timeStep: TimeStep, fluidType: FluidType, compareTimePeriod?: ITimePeriod ): Promise<IChartData | null> { let actualData: IDataload[] | null = [] let comparisonData: IDataload[] | null = [] let singleFluidGraphData: IChartData | null = null if (compareTimePeriod) { const result = await Promise.all([ this._queryRunner.fetchFluidData(timePeriod, timeStep, fluidType), this._queryRunner.fetchFluidData( compareTimePeriod, timeStep, fluidType ), ]) actualData = result[0] comparisonData = result[1] } else actualData = await this._queryRunner.fetchFluidData( timePeriod, timeStep, fluidType ) if (actualData) { singleFluidGraphData = { actualData: actualData, comparisonData: comparisonData, } } return singleFluidGraphData } private formatGraphDataManage( data: IChartData | null, timeStep: TimeStep, timePeriod: ITimePeriod, compareTimePeriod: ITimePeriod | null ): IChartData | null { if (!data) return null const formattedActualData: IDataload[] = this._consumptionDataFormatter.formatGraphData( data.actualData, timePeriod, timeStep ) let formattedComparisonData: IDataload[] | null = null if (compareTimePeriod) formattedComparisonData = this._consumptionDataFormatter.formatGraphData( data.comparisonData ? data.comparisonData : [], compareTimePeriod, timeStep ) const result: IChartData = { actualData: formattedActualData, comparisonData: formattedComparisonData, } return result } public async fetchLastDateData( fluidTypes: FluidType[], allFluids?: boolean ): Promise<DateTime | null> { let lastDay = null if (fluidTypes.length === 1) { lastDay = (await this._queryRunner.getLastDateData(fluidTypes[0])) || null } else if (fluidTypes.length > 1) { const lastDays = [] for (const fluidType of fluidTypes) { lastDay = (await this._queryRunner.getLastDateData(fluidType)) || null if (lastDay) { lastDays.push(lastDay) } } if (lastDays.length < 1) { return null } if (allFluids) { lastDay = lastDays.reduce(function(a, b) { return a < b ? a : b }) } else { lastDay = lastDays.reduce(function(a, b) { return a > b ? a : b }) } } //validate input // validate output return lastDay } public async checkDoctypeEntries( fluideType: FluidType, timeStep: TimeStep ): Promise<boolean> { const queryResult = await this._queryRunner.getEntries(fluideType, timeStep) if (queryResult.data.length > 0) { return true } return false } private aggregateGraphData( singleFluidCharts: ISingleFluidChartData[] //,withComparison: boolean = true ): IChartData | null { if (singleFluidCharts && singleFluidCharts[0].chartData) { const ltcc = new LoadToCurrencyConverter() const resultChartData: IChartData = { actualData: [], comparisonData: [], } let length = 0 if (singleFluidCharts[0].chartData.comparisonData) { length = Math.max( singleFluidCharts[0].chartData.comparisonData.length, singleFluidCharts[0].chartData.actualData.length ) } else length = singleFluidCharts[0].chartData.actualData.length for (let i = 0; i < length; i++) { //const agreggatedDate= singleFluidCharts[0].chartData.actualData[i].; let agreggatedConvertedValue = 0 let comparisonAgreggatedConvertedValue = 0 let noDataCount = 0 let comparisonNoDataCount = 0 const convertedValueDetail = [] const comparisonConvertedValueDetail = [] for (const singleFluidChart of singleFluidCharts) { if (!singleFluidChart.chartData) break const value = singleFluidChart.chartData.actualData[i].value if ( !singleFluidChart.chartData.comparisonData || !singleFluidChart.chartData.comparisonData[i] ) break const comparisonValue = singleFluidChart.chartData.comparisonData[i].value let convertedValue = -1 if (value === -1) noDataCount++ else { convertedValue = ltcc.Convert(value, singleFluidChart.chartFluid) agreggatedConvertedValue += convertedValue } let convertedComparisonValue = -1 if (comparisonValue === -1) comparisonNoDataCount++ else { convertedComparisonValue = ltcc.Convert( comparisonValue, singleFluidChart.chartFluid ) comparisonAgreggatedConvertedValue += convertedComparisonValue } convertedValueDetail[singleFluidChart.chartFluid] = convertedValue comparisonConvertedValueDetail[ singleFluidChart.chartFluid ] = convertedComparisonValue } if (singleFluidCharts.length === noDataCount) agreggatedConvertedValue = -1 if (singleFluidCharts.length === comparisonNoDataCount) comparisonAgreggatedConvertedValue = -1 if (singleFluidCharts[0].chartData.actualData[i]) { const acutaldataLoad: IDataload = { date: singleFluidCharts[0].chartData.actualData[i].date, value: agreggatedConvertedValue, valueDetail: agreggatedConvertedValue === -1 ? null : convertedValueDetail, } resultChartData.actualData.push(acutaldataLoad) } if ( singleFluidCharts[0].chartData.comparisonData && resultChartData.comparisonData && singleFluidCharts[0].chartData.comparisonData[i] ) { const comparisondataLoad: IDataload = { date: singleFluidCharts[0].chartData.comparisonData[i].date, value: comparisonAgreggatedConvertedValue, valueDetail: comparisonAgreggatedConvertedValue === -1 ? null : comparisonConvertedValueDetail, } resultChartData.comparisonData.push(comparisondataLoad) } } return resultChartData } return null } }