pricing  - price consumption chunks (reorg)
MODIFY
src/integration/pricing/ConsumptionChunkPricer.ts
src/integration/pricing/ConsumptionChunkPricer.ts
+import ConsumptionPricer, { ConsumptionChunk } from './ConsumptionPricer';
import { DimensionType, PricedConsumptionData, PricedDimensionData, PricingDimension, PricingRestriction, PricingTimeLimit, ResolvedPricingDefinition, ResolvedPricingModel } from '../../types/Pricing';
import Consumption from '../../types/Consumption';
-import ConsumptionPricer from './ConsumptionPricer';
-import PricingHelper from './PricingHelper';
import Utils from '../../utils/Utils';
import moment from 'moment';
-interface ConsumptionChunk{
- startedAt: Date;
- endedAt: Date;
- consumptionWh: number;
- cumulatedConsumptionWh: number;
- totalDurationSecs: number;
- totalInactivitySecs: number;
-}
-
-export default class ConsumptionChunkGenerator {
- consumptionPricer: ConsumptionPricer;
- constructor(consumptionPricer: ConsumptionPricer) {
- this.consumptionPricer = consumptionPricer;
- }
-
- public priceConsumption(): PricedConsumptionData {
- let accumulatedConsumptionData: PricedConsumptionData = {};
- for (const consumptionChunk of this.generateChunk()) {
- const pricedData = this.priceConsumptionChunk(consumptionChunk);
- accumulatedConsumptionData = PricingHelper.accumulatePricedConsumption([ accumulatedConsumptionData, pricedData ]);
- }
- return accumulatedConsumptionData;
- }
-
- private priceConsumptionChunk(consumptionChunk: ConsumptionChunk): PricedConsumptionData {
- const consumptionChunkPricer = new ConsumptionChunkPricer(this.consumptionPricer, consumptionChunk);
- return consumptionChunkPricer.priceConsumptionChunk();
- }
-
- private * generateChunk(): IterableIterator<ConsumptionChunk> {
- const consumptionData = this.consumptionPricer.consumptionData;
- const startedAt = moment(consumptionData.startedAt);
- const endedAt = moment(consumptionData.endedAt);
- const durationSecs = endedAt.diff(startedAt, 'seconds');
- if (durationSecs <= 60) {
- yield ConsumptionChunkPricer.convertToConsumptionChunk(consumptionData);
- } else {
- // Well - we got data for more than 1 minute! - we need to handle chunks!
- const nbSeconds = endedAt.diff(startedAt, 'seconds');
- let secondsAlreadyPriced = 0;
- let chunkCumulatedConsumptionWh = Utils.createDecimal(consumptionData.cumulatedConsumptionWh).minus(consumptionData.consumptionWh);
- let chunkTotalDurationSecs = Utils.createDecimal(consumptionData.totalDurationSecs).minus(durationSecs);
- while (secondsAlreadyPriced < nbSeconds) {
- // Number of seconds for the current chunk
- const secondsToPrice = Utils.minValue(nbSeconds - secondsAlreadyPriced, 60);
- // Chunk dates
- const chunkStartedAt = moment(startedAt).add(secondsAlreadyPriced, 'seconds');
- const chunkEndedAt = moment(startedAt).add(secondsAlreadyPriced + secondsToPrice, 'seconds');
- // Chunk consumption
- const chunkConsumptionWh = Utils.createDecimal(consumptionData.consumptionWh).mul(secondsToPrice).divToInt(nbSeconds);
- // Chunk accumulated data
- chunkCumulatedConsumptionWh = chunkCumulatedConsumptionWh.add(chunkConsumptionWh);
- chunkTotalDurationSecs = chunkTotalDurationSecs.plus(secondsToPrice);
- // Create the consumption chunk
- const consumptionChunk = {
- startedAt: chunkStartedAt.toDate(),
- endedAt: chunkEndedAt.toDate(),
- consumptionWh: chunkConsumptionWh.toNumber(),
- cumulatedConsumptionWh: chunkCumulatedConsumptionWh.toNumber(),
- totalDurationSecs: chunkTotalDurationSecs.toNumber(),
- totalInactivitySecs: 0 // TO BE CLARIFIED
- };
- // Number of seconds already priced
- secondsAlreadyPriced += secondsToPrice;
- // Production of the chunk
- yield consumptionChunk;
- }
- }
- }
-}
-
-class ConsumptionChunkPricer {
+export default class ConsumptionChunkPricer {
consumptionPricer: ConsumptionPricer;
consumptionChunk: ConsumptionChunk;
actualPricingDefinitions: ResolvedPricingDefinition[];

MODIFY
src/integration/pricing/ConsumptionPricer.ts
src/integration/pricing/ConsumptionPricer.ts
-import { PricedConsumptionData, PricingRestriction, PricingTimeLimit, ResolvedPricingDefinition, ResolvedPricingModel } from '../../types/Pricing';
+import { PricedConsumptionData, ResolvedPricingDefinition, ResolvedPricingModel } from '../../types/Pricing';
import Consumption from '../../types/Consumption';
-import ConsumptionChunkGenerator from './ConsumptionChunkPricer';
+import ConsumptionChunkPricer from './ConsumptionChunkPricer';
+import PricingHelper from './PricingHelper';
import Tenant from '../../types/Tenant';
import Utils from '../../utils/Utils';
import moment from 'moment';
-export default class ConsumptionPricer {
+export interface ConsumptionChunk{
+ startedAt: Date;
+ endedAt: Date;
+ consumptionWh: number;
+ cumulatedConsumptionWh: number;
+ totalDurationSecs: number;
+ totalInactivitySecs: number;
+}
+export default class ConsumptionPricer {
tenant: Tenant;
pricingModel: ResolvedPricingModel;
consumptionData: Consumption;
@@ -17,109 +26,65 @@ export default class ConsumptionPricer {
this.tenant = tenant;
this.pricingModel = pricingModel;
this.consumptionData = consumptionData;
- const actualPricingDefinitions = this.pricingModel.pricingDefinitions.filter((pricingDefinition) =>
- this.checkPricingDefinitionRestrictions(pricingDefinition)
- );
- // It does not make sense to apply several tariffs to a single consumption
- this.actualPricingDefinitions = [ actualPricingDefinitions[0] ];
}
public getPricingModel(): ResolvedPricingModel {
return this.pricingModel;
}
- public getPricingDefinitions(): ResolvedPricingDefinition[] {
- return this.actualPricingDefinitions;
- }
-
public priceConsumption(): PricedConsumptionData {
- const chunkGenerator = new ConsumptionChunkGenerator(this);
- return chunkGenerator.priceConsumption();
- }
-
- private checkPricingDefinitionRestrictions(pricingDefinition: ResolvedPricingDefinition) : ResolvedPricingDefinition {
- if (pricingDefinition.restrictions) {
- if (!this.checkDayOfWeek(pricingDefinition.restrictions)
- || !this.checkTimeValidity(pricingDefinition.restrictions)
- || !this.checkMinEnergy(pricingDefinition.restrictions)
- || !this.checkMaxEnergy(pricingDefinition.restrictions)
- || !this.checkMinDuration(pricingDefinition.restrictions)
- || !this.checkMaxDuration(pricingDefinition.restrictions)) {
- return null;
+ let accumulatedConsumptionData: PricedConsumptionData = {};
+ for (const consumptionChunk of this.generateChunk()) {
+ const pricedData = this.priceConsumptionChunk(consumptionChunk);
+ accumulatedConsumptionData = PricingHelper.accumulatePricedConsumption([ accumulatedConsumptionData, pricedData ]);
}
- }
- // a definition matching the restrictions has been found
- return pricingDefinition;
+ return accumulatedConsumptionData;
}
- private checkDayOfWeek(restrictions: PricingRestriction): boolean {
- if (!Utils.isNullOrUndefined(restrictions.daysOfWeek)) {
- const timezone = this.pricingModel.pricerContext.timezone;
- if (!timezone) {
- // Charging station timezone is not known - 'days of the week' restrictions cannot be used in such context
- return false;
- }
- // ACHTUNG - moment.isoWeekday() - 1:MONDAY, 7: SUNDAY
- return restrictions.daysOfWeek.includes(moment(this.consumptionData.startedAt).tz(timezone).isoWeekday());
- }
- // No restrictions related to the day of the week
- return true;
+ private priceConsumptionChunk(consumptionChunk: ConsumptionChunk): PricedConsumptionData {
+ const consumptionChunkPricer = new ConsumptionChunkPricer(this, consumptionChunk);
+ return consumptionChunkPricer.priceConsumptionChunk();
}
- private checkTimeValidity(restrictions: PricingRestriction): boolean {
- // The time range restriction must consider the charging station timezone
- if (Utils.isNullOrUndefined(restrictions.timeFrom) && Utils.isNullOrUndefined(restrictions.timeTo)) {
- // No time restrictions
- return true;
+ private * generateChunk(): IterableIterator<ConsumptionChunk> {
+ const consumptionData = this.consumptionData;
+ const startedAt = moment(consumptionData.startedAt);
+ const endedAt = moment(consumptionData.endedAt);
+ const durationSecs = endedAt.diff(startedAt, 'seconds');
+ if (durationSecs <= 60) {
+ yield ConsumptionChunkPricer.convertToConsumptionChunk(consumptionData);
+ } else {
+ // Well - we got data for more than 1 minute! - we need to handle chunks!
+ const nbSeconds = endedAt.diff(startedAt, 'seconds');
+ let secondsAlreadyPriced = 0;
+ let chunkCumulatedConsumptionWh = Utils.createDecimal(consumptionData.cumulatedConsumptionWh).minus(consumptionData.consumptionWh);
+ let chunkTotalDurationSecs = Utils.createDecimal(consumptionData.totalDurationSecs).minus(durationSecs);
+ while (secondsAlreadyPriced < nbSeconds) {
+ // Number of seconds for the current chunk
+ const secondsToPrice = Utils.minValue(nbSeconds - secondsAlreadyPriced, 60);
+ // Chunk dates
+ const chunkStartedAt = moment(startedAt).add(secondsAlreadyPriced, 'seconds');
+ const chunkEndedAt = moment(startedAt).add(secondsAlreadyPriced + secondsToPrice, 'seconds');
+ // Chunk consumption
+ const chunkConsumptionWh = Utils.createDecimal(consumptionData.consumptionWh).mul(secondsToPrice).divToInt(nbSeconds);
+ // Chunk accumulated data
+ chunkCumulatedConsumptionWh = chunkCumulatedConsumptionWh.add(chunkConsumptionWh);
+ chunkTotalDurationSecs = chunkTotalDurationSecs.plus(secondsToPrice);
+ // Create the consumption chunk
+ const consumptionChunk = {
+ startedAt: chunkStartedAt.toDate(),
+ endedAt: chunkEndedAt.toDate(),
+ consumptionWh: chunkConsumptionWh.toNumber(),
+ cumulatedConsumptionWh: chunkCumulatedConsumptionWh.toNumber(),
+ totalDurationSecs: chunkTotalDurationSecs.toNumber(),
+ totalInactivitySecs: 0 // TO BE CLARIFIED
+ };
+ // Number of seconds already priced
+ secondsAlreadyPriced += secondsToPrice;
+ // Production of the chunk
+ yield consumptionChunk;
}
- // The time range has to consider the time zone of the charging station
- const timezone = this.pricingModel.pricerContext.timezone;
- if (!timezone) {
- // Charging station timezone is not known - time restrictions cannot be used in such context
- return false;
- }
- // Normalize time range restrictions - both limits must be set!
- const timeFrom = PricingTimeLimit.parseTime(restrictions.timeFrom || '00:00');
- const timeTo = PricingTimeLimit.parseTime(restrictions.timeTo || '00:00');
- // Time of the consumption in its timezone
- const consumptionStartTime = PricingTimeLimit.parseTime(moment(this.consumptionData.startedAt).tz(timezone).format('HH:mm:ss'));
- return consumptionStartTime.isBetween(timeFrom, timeTo);
- }
-
- private checkMinEnergy(restrictions: PricingRestriction): boolean {
- if (!Utils.isNullOrUndefined(restrictions.minEnergyKWh)) {
- if (Utils.createDecimal(this.consumptionData.cumulatedConsumptionWh).div(1000).lessThan(restrictions.minEnergyKWh)) {
- return false;
}
}
- return true;
}
- private checkMaxEnergy(restrictions: PricingRestriction): boolean {
- if (!Utils.isNullOrUndefined(restrictions.maxEnergyKWh)) {
- if (Utils.createDecimal(this.consumptionData.cumulatedConsumptionWh).div(1000).greaterThanOrEqualTo(restrictions.maxEnergyKWh)) {
- return false;
- }
- }
- return true;
- }
-
- private checkMinDuration(restrictions: PricingRestriction): boolean {
- if (!Utils.isNullOrUndefined(restrictions.minDurationSecs)) {
- if (Utils.createDecimal(this.consumptionData.totalDurationSecs).lessThan(restrictions.minDurationSecs)) {
- return false;
- }
- return true;
- }
- return true;
- }
-
- private checkMaxDuration(restrictions: PricingRestriction): boolean {
- if (!Utils.isNullOrUndefined(restrictions.maxDurationSecs)) {
- if (Utils.createDecimal(this.consumptionData.totalDurationSecs).greaterThanOrEqualTo(restrictions.maxDurationSecs)) {
- return false;
- }
- }
- return true;
- }
-}

pricing  - price consumption chunks (reorg)