import { Client, QueryDefinition } from 'cozy-client' import { DateTime, Interval } from 'luxon' import { ENEDIS_DAY_DOCTYPE, ENEDIS_MONTH_DOCTYPE, ENEDIS_YEAR_DOCTYPE, GRDF_DAY_DOCTYPE, GRDF_MONTH_DOCTYPE, GRDF_YEAR_DOCTYPE, EGL_DAY_DOCTYPE, EGL_MONTH_DOCTYPE, EGL_YEAR_DOCTYPE, ENEDIS_MINUTE_DOCTYPE, GRDF_HOUR_DOCTYPE, } from 'doctypes' import { FluidType } from 'enum/fluid.enum' import { TimeStep, ITimePeriod, IDataload } from './dataConsumptionContracts' export class QueryRunner { // TODO to be clean up private readonly _max_limit = 1000 private readonly _default_months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] private readonly _default_days = [ 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, ] private readonly _client: Client constructor(_client: Client) { this._client = _client } public async fetchFluidData( timePeriod: ITimePeriod, timeStep: TimeStep, fluidType: FluidType ): Promise<IDataload[] | null> { const result = await this.fetchData( this.buildListQuery(timeStep, timePeriod, fluidType, this._max_limit) ) if (result && result.data) { const filteredResult = this.filterDataList(result, timePeriod) const mappedResult = this.mapDataList(filteredResult) return mappedResult } return null } public async getLastDateData(fluidType: FluidType): Promise<DateTime | null> { const result = await this.fetchData(this.buildLastDateQuery(fluidType, 1)) if ( result && result.data && result.data[0] && result.data[0].year && result.data[0].month && result.data[0].day ) { return DateTime.local( result.data[0].year, result.data[0].month, result.data[0].day ) } return null } private async fetchData(query: QueryDefinition) { let result = null try { result = await this._client.query(query) } catch (error) { // log stuff //throw new Error("Cozy Cient Error"); return null } return result } public async getEntries(fluidType: FluidType[], timeStep: TimeStep) { let result = null try { result = await this._client.find(ENEDIS_DAY_DOCTYPE).where({}) } catch (error) { return null } return result } private filterDataList(data, timePeriod: ITimePeriod) { const filteredResult = data.data.filter(entry => this.withinDateBoundaries( DateTime.local( entry.year, entry.month === 0 ? 1 : entry.month, entry.day === 0 ? 1 : entry.day ), timePeriod ) ) return filteredResult } private mapDataList(data) { const mappedResult = data.map(entry => ({ date: DateTime.local( entry.year, entry.month == 0 ? 1 : entry.month, entry.day === 0 ? 1 : entry.day, entry.hour, entry.minute ), value: entry.load, })) return mappedResult } private buildListQuery( timeStep: TimeStep, timePeriod: ITimePeriod, fluidType: FluidType, limit: number ) { const doctype = this.getRelevantDoctype(fluidType, timeStep) return this._client .find(doctype) .where(this.getPredicate(timePeriod, timeStep)) .limitBy(limit) } private withinDateBoundaries(dateTime: DateTime, timePeriod: ITimePeriod) { return dateTime <= timePeriod.endDate && dateTime >= timePeriod.startDate } private getInBetweenMonths(timePeriod: ITimePeriod) { const intervalCount = Interval.fromDateTimes( timePeriod.startDate, timePeriod.endDate ).count('month') if (intervalCount >= 12) return this._default_months const monthList = [] let offset = 0 if (timePeriod.startDate.year !== timePeriod.endDate.year) offset = 12 for ( let m = timePeriod.startDate.month; m <= timePeriod.endDate.month + offset; m++ ) monthList.push(m > offset ? m - offset : m) return monthList } private getInBetweenDays(timePeriod: ITimePeriod) { const intervalCount = Interval.fromDateTimes( timePeriod.startDate, timePeriod.endDate ).count('day') if (intervalCount >= 31) return this._default_days const dayList = [] let offset = 0 if (timePeriod.startDate.month !== timePeriod.endDate.month) offset = 31 for ( let d = timePeriod.startDate.day; d <= timePeriod.endDate.day + offset; d++ ) dayList.push(d > offset ? d - offset : d) return dayList } private buildLastDateQuery(fluidType: FluidType, limit: number) { const doctype = this.getRelevantDoctype(fluidType, TimeStep.DAY) return this._client .find(doctype) .where({}) .indexFields(['year', 'month', 'day']) .sortBy([{ year: 'desc' }, { month: 'desc' }, { day: 'desc' }]) .limitBy(1) } private getPredicate(timePeriod: ITimePeriod, timeStep: TimeStep) { let predicate = {} switch (timeStep) { case TimeStep.HALF_AN_HOUR: predicate = { year: { $eq: timePeriod.startDate.year, }, month: { $eq: timePeriod.startDate.month, }, day: { $eq: timePeriod.startDate.day, }, } break case TimeStep.DAY: predicate = { year: { $lte: timePeriod.endDate.year, $gte: timePeriod.startDate.year, }, month: { $in: this.getInBetweenMonths(timePeriod), }, day: { $in: this.getInBetweenDays(timePeriod), }, } break case TimeStep.MONTH: predicate = { year: { $lte: timePeriod.endDate.year, $gte: timePeriod.startDate.year, }, month: { $in: this.getInBetweenMonths(timePeriod), }, } break case TimeStep.YEAR: predicate = { year: { $lte: timePeriod.endDate.year, $gte: timePeriod.startDate.year, }, } break } return predicate } private getRelevantDoctype(fluidType: FluidType, timeStep: TimeStep) { let doctype = '' switch (fluidType) { case FluidType.ELECTRICITY: { switch (timeStep) { case TimeStep.HALF_AN_HOUR: doctype = ENEDIS_MINUTE_DOCTYPE break case TimeStep.DAY: doctype = ENEDIS_DAY_DOCTYPE break case TimeStep.MONTH: doctype = ENEDIS_MONTH_DOCTYPE break case TimeStep.YEAR: doctype = ENEDIS_YEAR_DOCTYPE break default: doctype = ENEDIS_DAY_DOCTYPE } } break case FluidType.WATER: { switch (timeStep) { case TimeStep.DAY: doctype = EGL_DAY_DOCTYPE break case TimeStep.MONTH: doctype = EGL_MONTH_DOCTYPE break case TimeStep.YEAR: doctype = EGL_YEAR_DOCTYPE break default: doctype = EGL_DAY_DOCTYPE } } break case FluidType.GAS: { switch (timeStep) { case TimeStep.HOUR: doctype = GRDF_HOUR_DOCTYPE break case TimeStep.DAY: doctype = GRDF_DAY_DOCTYPE break case TimeStep.MONTH: doctype = GRDF_MONTH_DOCTYPE break case TimeStep.YEAR: doctype = GRDF_YEAR_DOCTYPE break default: doctype = GRDF_DAY_DOCTYPE } } break default: break } return doctype } }