Used Ramer Douglas Peucker algo to optimize the charging curve
MODIFY
package-lock.json
package-lock.json
"bluebird": "^3.7.2",
"body-parser": "^1.20.0",
"body-parser-xml": "^2.0.3",
- "bufferutil": "^4.0.6",
"busboy": "^1.4.0",
"chalk": "^4.1.2",
"cors": "^2.8.5",
"countries-list": "^2.6.1",
"csvtojson": "^2.0.10",
+ "curvereduce": "^0.0.4",
"decimal.js": "^10.3.1",
"ejs": "^3.1.8",
"emailjs": "^3.7.0",
"fast-bind": "^1.0.0"
}
},
+ "node_modules/curvereduce": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/curvereduce/-/curvereduce-0.0.4.tgz",
+ "integrity": "sha512-+ClFGRSuCK51lG5ttg1xr46QjQbvfsjvPrbj908jWXVq/++aveqop5kHmEMYAhRmtXwNv53iwbeNWfcdURD3jQ=="
+ },
"node_modules/cwise-compiler": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz",
"fast-bind": "^1.0.0"
}
},
+ "curvereduce": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/curvereduce/-/curvereduce-0.0.4.tgz",
+ "integrity": "sha512-+ClFGRSuCK51lG5ttg1xr46QjQbvfsjvPrbj908jWXVq/++aveqop5kHmEMYAhRmtXwNv53iwbeNWfcdURD3jQ=="
+ },
"cwise-compiler": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz",

MODIFY
package.json
package.json
"cors": "^2.8.5",
"countries-list": "^2.6.1",
"csvtojson": "^2.0.10",
+ "curvereduce": "^0.0.4",
"decimal.js": "^10.3.1",
"ejs": "^3.1.8",
"emailjs": "^3.7.0",

MODIFY
src/client/ocpi/CpoOCPIClient.ts
src/client/ocpi/CpoOCPIClient.ts
@@ -1039,7 +1039,7 @@ export default class CpoOCPIClient extends OCPIClient {
tenant, { transactionId: transaction.id });
if (consumptions) {
// Build based on consumptions
- for (const consumption of consumptions) {
+ for (const consumption of consumptions.result) {
const chargingPeriod = this.buildChargingPeriod(consumption);
if (!Utils.isEmptyArray(chargingPeriod?.dimensions)) {
chargingPeriods.push(chargingPeriod);

MODIFY
src/server/rest/v1/service/TransactionService.ts
src/server/rest/v1/service/TransactionService.ts
@@ -531,14 +531,14 @@ export default class TransactionService {
);
consumptions = consumptionsMDB.result;
} else {
- consumptions = await ConsumptionStorage.getOptimizedTransactionConsumptions(
+ consumptions = (await ConsumptionStorage.getOptimizedTransactionConsumptions(
req.tenant, { transactionId: transaction.id }, [
'startedAt', 'endedAt', 'cumulatedConsumptionWh', 'cumulatedConsumptionAmps', 'cumulatedAmount',
'stateOfCharge', 'limitWatts', 'limitAmps',
'instantVoltsDC', 'instantVolts', 'instantVoltsL1', 'instantVoltsL2', 'instantVoltsL3',
'instantWattsDC', 'instantWatts', 'instantWattsL1', 'instantWattsL2', 'instantWattsL3',
'instantAmpsDC', 'instantAmps', 'instantAmpsL1', 'instantAmpsL2', 'instantAmpsL3'
- ]);
+ ])).result;
}
// Assign
transaction.values = consumptions;
@@ -593,7 +593,7 @@ export default class TransactionService {
['startedAt', 'endedAt', 'cumulatedConsumptionWh']
);
// Convert consumptions to the ADVENIR format
- const advenirValues: AdvenirConsumptionData[] = consumptions.map(
+ const advenirValues: AdvenirConsumptionData[] = consumptions.result.map(
(consumption) => {
// Unix epoch format expected
const timestamp = Utils.createDecimal(consumption.startedAt.getTime()).div(1000).toNumber();

MODIFY
src/storage/mongodb/ConsumptionStorage.ts
src/storage/mongodb/ConsumptionStorage.ts
+import { Point, Simplify, SimplifyTo } from 'curvereduce';
import global, { FilterParams, GroupParams } from '../../types/GlobalType';
import Constants from '../../utils/Constants';
@@ -10,6 +11,7 @@ import { SiteAreaValueTypes } from '../../types/SiteArea';
import Tenant from '../../types/Tenant';
import { UpdateResult } from 'mongodb';
import Utils from '../../utils/Utils';
+import sizeof from 'object-sizeof';
const MODULE_NAME = 'ConsumptionStorage';
@@ -530,6 +532,38 @@ export default class ConsumptionStorage {
};
}
+ public static async getOptimizedTransactionConsumptions(tenant: Tenant, params: { transactionId: number }, projectFields?: string[]): Promise<DataResult<Consumption>> {
+ // Get all consumptions
+ const consumptionsMDB = await ConsumptionStorage.getTransactionConsumptions(
+ tenant, params, Constants.DB_PARAMS_MAX_LIMIT, projectFields);
+ const startTime = Logging.traceDatabaseRequestStart();
+ // Optimize the curves
+ // Create the Points
+ const consumptionsXY: Point[] = consumptionsMDB.result.map((consumptionMDB) => (
+ {
+ x: consumptionMDB.endedAt.getTime(),
+ // Convert Watts to kWatts to have the same proportion as the amount to detect the variation
+ y:
+ (consumptionMDB.instantWatts ?? 0) / 1000 +
+ (consumptionMDB.instantWattsDC ?? 0) / 1000 +
+ (consumptionMDB.cumulatedAmount ?? 0) +
+ (consumptionMDB.cumulatedConsumptionWh ?? 0) / 1000
+ }
+ ));
+ // Simplify with Ramer Douglas Peucker algo
+ const simplifiedConsumptionsXY = Simplify(consumptionsXY, 5);
+ // Create a Map to reference Y points
+ const simplifiedConsumptionsMapXY = new Map<number, null>();
+ for (const simplifiedConsumptionXY of simplifiedConsumptionsXY) {
+ simplifiedConsumptionsMapXY.set(simplifiedConsumptionXY.x, null);
+ }
+ // Filter Consumptions
+ consumptionsMDB.result = consumptionsMDB.result.filter((consumptionMDB) =>
+ simplifiedConsumptionsMapXY.has(consumptionMDB.endedAt.getTime()));
+ await Logging.traceDatabaseRequestEnd(tenant, MODULE_NAME, 'getOptimizedTransactionConsumptions', startTime, consumptionsMDB);
+ return consumptionsMDB;
+ }
+
public static async getLastTransactionConsumption(tenant: Tenant, params: { transactionId: number }): Promise<Consumption> {
const startTime = Logging.traceDatabaseRequestStart();
DatabaseUtils.checkTenantObject(tenant);
@@ -566,110 +600,6 @@ export default class ConsumptionStorage {
return consumption;
}
- public static async getOptimizedTransactionConsumptions(tenant: Tenant, params: { transactionId: number }, projectFields?: string[]): Promise<Consumption[]> {
- const startTime = Logging.traceDatabaseRequestStart();
- DatabaseUtils.checkTenantObject(tenant);
- // Create Aggregation
- const aggregation = [];
- // Filters
- aggregation.push({
- $match: {
- transactionId: Utils.convertToInt(params.transactionId)
- }
- });
- // Group by
- aggregation.push({
- $group: {
- _id: {
- year: { '$year': '$endedAt' },
- month: { '$month': '$endedAt' },
- day: { '$dayOfMonth': '$endedAt' },
- hour: { '$hour': '$endedAt' },
- minute: { '$minute': '$endedAt' }
- },
- consumptions :{
- $first: {
- 'cumulatedConsumptionWh': '$cumulatedConsumptionWh',
- 'cumulatedConsumptionAmps': '$cumulatedConsumptionAmps',
- 'cumulatedAmount': '$cumulatedAmount',
- 'stateOfCharge': '$stateOfCharge',
- 'limitWatts': '$limitWatts',
- 'limitAmps': '$limitAmps',
- 'instantVoltsDC': '$instantVoltsDC',
- 'instantVolts': '$instantVolts',
- 'instantVoltsL1': '$instantVoltsL1',
- 'instantVoltsL2': '$instantVoltsL2',
- 'instantVoltsL3': '$instantVoltsL3',
- 'instantWattsDC': '$instantWattsDC',
- 'instantWatts': '$instantWatts',
- 'instantWattsL1': '$instantWattsL1',
- 'instantWattsL2': '$instantWattsL2',
- 'instantWattsL3': '$instantWattsL3',
- 'instantAmpsDC': '$instantAmpsDC',
- 'instantAmps': '$instantAmps',
- 'instantAmpsL1': '$instantAmpsL1',
- 'instantAmpsL2': '$instantAmpsL2',
- 'instantAmpsL3': '$instantAmpsL3',
- 'startedAt': '$startedAt',
- 'endedAt': '$endedAt',
- }
- }
- }
- });
- // Rename fields to original fields
- aggregation.push({
- $addFields: {
- 'cumulatedConsumptionWh': '$consumptions.cumulatedConsumptionWh',
- 'cumulatedConsumptionAmps': '$consumptions.cumulatedConsumptionAmps',
- 'cumulatedAmount': '$consumptions.cumulatedAmount',
- 'stateOfCharge': '$consumptions.stateOfCharge',
- 'limitWatts': '$consumptions.limitWatts',
- 'limitAmps': '$consumptions.limitAmps',
- 'instantVoltsDC': '$consumptions.instantVoltsDC',
- 'instantVolts': '$consumptions.instantVolts',
- 'instantVoltsL1': '$consumptions.instantVoltsL1',
- 'instantVoltsL2': '$consumptions.instantVoltsL2',
- 'instantVoltsL3': '$consumptions.instantVoltsL3',
- 'instantWattsDC': '$consumptions.instantWattsDC',
- 'instantWatts': '$consumptions.instantWatts',
- 'instantWattsL1': '$consumptions.instantWattsL1',
- 'instantWattsL2': '$consumptions.instantWattsL2',
- 'instantWattsL3': '$consumptions.instantWattsL3',
- 'instantAmpsDC': '$consumptions.instantAmpsDC',
- 'instantAmps': '$consumptions.instantAmps',
- 'instantAmpsL1': '$consumptions.instantAmpsL1',
- 'instantAmpsL2': '$consumptions.instantAmpsL2',
- 'instantAmpsL3': '$consumptions.instantAmpsL3',
- 'startedAt': '$consumptions.startedAt',
- 'endedAt': '$consumptions.endedAt',
- }
- });
- // Remove _id from projected fields
- aggregation.push({
- $project: {
- _id: 0,
- consumptions: 0,
- }
- });
- // Project
- DatabaseUtils.projectFields(aggregation, projectFields);
- // Sort
- aggregation.push({
- $sort: {
- 'endedAt': 1,
- }
- });
- // Read DB
- const consumptionsMDB = await global.database.getCollection<Consumption>(tenant.id, 'consumptions')
- .aggregate<Consumption>(aggregation, DatabaseUtils.buildAggregateOptions())
- .toArray();
- const lastElement = consumptionsMDB.pop();
- const consumptions = consumptionsMDB.filter((elem, index) => index % 5 === 0 ? true : false);
- consumptions.push(lastElement);
- await Logging.traceDatabaseRequestEnd(tenant, MODULE_NAME, 'getOptimizedTransactionConsumptions', startTime, aggregation, consumptions);
- return consumptions;
- }
-
private static buildConsumptionMDB(consumption: Consumption): any {
// Set the ID
if (!consumption.id) {

Used Ramer Douglas Peucker algo to optimize the charging curve