Skip to content
Snippets Groups Projects
consumptionDataManagerService.ts 11.6 KiB
Newer Older
Hugo NOUTS's avatar
Hugo NOUTS committed
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(
Hugo NOUTS's avatar
Hugo NOUTS committed
    fluideType: FluidType,
    timeStep: TimeStep
  ): Promise<boolean> {
    const queryResult = await this._queryRunner.getEntries(fluideType, timeStep)
Hugo NOUTS's avatar
Hugo NOUTS committed
    if (queryResult.length > 0) {
      return true
    }
    return false
Hugo NOUTS's avatar
Hugo NOUTS committed
  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
  }
}