@etainabl/nodejs-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,29 @@
1
+ module.exports = {
2
+ extends: ['airbnb-base'],
3
+ parser: '@typescript-eslint/parser',
4
+ rules: {
5
+ 'no-console': 'off',
6
+ 'comma-dangle': [2, 'never'],
7
+ 'arrow-parens': [2, 'as-needed'],
8
+ 'linebreak-style': 0,
9
+ 'padded-blocks': 0,
10
+ 'no-underscore-dangle': [0],
11
+ 'no-param-reassign': [2, { props: false }],
12
+ 'new-cap': [2, { capIsNewExceptions: ['Router', 'ObjectId'] }],
13
+ 'no-tabs': 2,
14
+ 'max-len': [1, 150],
15
+ 'no-plusplus': 0,
16
+ 'generator-star-spacing': 0,
17
+ 'class-methods-use-this': 0,
18
+ 'import/extensions': 0,
19
+ 'import/no-unresolved': 0,
20
+ 'prefer-destructuring': 0,
21
+ 'no-use-before-define': 0,
22
+ 'import/prefer-default-export': 'off'
23
+ },
24
+ env: {
25
+ node: true,
26
+ browser: true
27
+ },
28
+ parserOptions: { ecmaVersion: 2020 }
29
+ };
package/api.js ADDED
@@ -0,0 +1,119 @@
1
+ import axios from 'axios';
2
+ import logger from './logger.js';
3
+ const log = logger('etainablApi');
4
+ const factory = {
5
+ get: (etainablApi, endpoint, postEndpoint) => async (id, options = {}) => {
6
+ log.info(`API Request: GET ${process.env.ETAINABL_API_URL}/${endpoint}/${id}${postEndpoint ? `/${postEndpoint}` : ''}`);
7
+ const response = await etainablApi.get(`${endpoint}/${id}${postEndpoint ? `/${postEndpoint}` : ''}`, options);
8
+ if (response.status !== 200) {
9
+ throw new Error(`${response.status} ${response.statusText} response from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
10
+ }
11
+ if (!response.data) {
12
+ throw new Error(`No data from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
13
+ }
14
+ return response.data;
15
+ },
16
+ list: (etainablApi, endpoint) => async (options = {}) => {
17
+ log.info(`API Request: GET ${process.env.ETAINABL_API_URL}/${endpoint}`);
18
+ const response = await etainablApi.get(`${endpoint}`, options);
19
+ if (response.status !== 200) {
20
+ throw new Error(`${response.status} ${response.statusText} response from API (GET ${endpoint})`);
21
+ }
22
+ if (!response.data || !response.data.data) {
23
+ throw new Error(`No data from API (GET ${endpoint})`);
24
+ }
25
+ return response.data;
26
+ },
27
+ update: (etainablApi, endpoint) => async (id, data, options = {}) => {
28
+ log.info(`API Request: PATCH ${process.env.ETAINABL_API_URL}/${endpoint}/${id}`);
29
+ const response = await etainablApi.patch(`${endpoint}/${id}`, data, options);
30
+ if (response.status !== 200) {
31
+ throw new Error(`${response.status} ${response.statusText} response from API (PATCH ${endpoint})`);
32
+ }
33
+ if (!response.data) {
34
+ throw new Error(`No data from API (PATCH ${endpoint})`);
35
+ }
36
+ return response.data;
37
+ },
38
+ create: (etainablApi, endpoint) => async (data, options = {}) => {
39
+ log.info(`API Request: POST ${process.env.ETAINABL_API_URL}/${endpoint}`);
40
+ const response = await etainablApi.post(`${endpoint}`, data, options);
41
+ if (response.status !== 200) {
42
+ throw new Error(`${response.status} ${response.statusText} response from API (POST ${endpoint})`);
43
+ }
44
+ if (!response.data) {
45
+ throw new Error(`No data from API (POST ${endpoint})`);
46
+ }
47
+ return response.data;
48
+ },
49
+ remove: (etainablApi, endpoint) => async (id, options = {}) => {
50
+ log.info(`API Request: DELETE ${process.env.ETAINABL_API_URL}/${endpoint}/${id}`);
51
+ const response = await etainablApi.delete(`${endpoint}/${id}`, options);
52
+ if (response.status !== 200) {
53
+ throw new Error(`${response.status} ${response.statusText} response from API (DELETE ${endpoint})`);
54
+ }
55
+ if (!response.data) {
56
+ throw new Error(`No data from API (DELETE ${endpoint})`);
57
+ }
58
+ return response.data;
59
+ }
60
+ };
61
+ export default (auth, instanceOptions = {}) => {
62
+ const headers = {};
63
+ if (auth.key) {
64
+ headers['x-key'] = auth.key;
65
+ }
66
+ else if (auth.token) {
67
+ headers['Authorization'] = auth.token;
68
+ }
69
+ else {
70
+ headers['x-key'] = process.env.ETAINABL_API_KEY;
71
+ }
72
+ const etainablApi = axios.create({
73
+ baseURL: process.env.ETAINABL_API_URL,
74
+ timeout: 30000,
75
+ headers,
76
+ ...instanceOptions
77
+ });
78
+ return {
79
+ instance: etainablApi,
80
+ // accounts
81
+ getAccount: factory.get(etainablApi, 'accounts'),
82
+ listAccounts: factory.list(etainablApi, 'accounts'),
83
+ updateAccount: factory.update(etainablApi, 'accounts'),
84
+ createAccount: factory.create(etainablApi, 'accounts'),
85
+ removeAccount: factory.remove(etainablApi, 'accounts'),
86
+ // assets
87
+ getAsset: factory.get(etainablApi, 'assets'),
88
+ listAssets: factory.list(etainablApi, 'assets'),
89
+ updateAsset: factory.update(etainablApi, 'assets'),
90
+ createAsset: factory.create(etainablApi, 'assets'),
91
+ removeAsset: factory.remove(etainablApi, 'assets'),
92
+ // assetGroups
93
+ getAssetGroup: factory.get(etainablApi, 'asset-groups'),
94
+ listAssetGroups: factory.list(etainablApi, 'asset-groups'),
95
+ updateAssetGroup: factory.update(etainablApi, 'asset-groups'),
96
+ createAssetGroup: factory.create(etainablApi, 'asset-groups'),
97
+ removeAssetGroup: factory.remove(etainablApi, 'asset-groups'),
98
+ getAssetGroupAssets: factory.get(etainablApi, 'asset-groups', 'assets'),
99
+ // automation
100
+ getAutomation: factory.get(etainablApi, 'automation'),
101
+ listAutomations: factory.list(etainablApi, 'automation'),
102
+ updateAutomation: factory.update(etainablApi, 'automation'),
103
+ createAutomation: factory.create(etainablApi, 'automation'),
104
+ removeAutomation: factory.remove(etainablApi, 'automation'),
105
+ // consumption
106
+ getConsumption: factory.get(etainablApi, 'consumptions'),
107
+ listConsumptions: factory.list(etainablApi, 'consumptions'),
108
+ updateConsumption: factory.update(etainablApi, 'consumptions'),
109
+ createConsumption: factory.create(etainablApi, 'consumptions'),
110
+ removeConsumption: factory.remove(etainablApi, 'consumptions'),
111
+ // readings
112
+ getReading: factory.get(etainablApi, 'readings'),
113
+ listReadings: factory.list(etainablApi, 'readings'),
114
+ updateReading: factory.update(etainablApi, 'readings'),
115
+ createReading: factory.create(etainablApi, 'readings'),
116
+ removeReading: factory.remove(etainablApi, 'readings'),
117
+ };
118
+ };
119
+ //# sourceMappingURL=api.js.map
package/api.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["src/api.ts"],"names":[],"mappings":"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"}
package/consumption.js ADDED
@@ -0,0 +1,47 @@
1
+ import moment from 'moment';
2
+ import { interpolate } from './lib/readingsInterpolate.js';
3
+ const apportionedReadings = (readings, granularity = 'days', interpolationMethod = 'linear', startDate, endDate) => {
4
+ // Readings sorted from newest to oldest
5
+ const sortedReadings = [...readings].sort((a, b) => moment(a.submittedAt).diff(b.submittedAt));
6
+ // Array of every hour/day etc between the first and last reading
7
+ const intervals = moment(sortedReadings[sortedReadings.length - 1].submittedAt).diff(moment(sortedReadings[0].submittedAt), granularity);
8
+ const dates = [...Array(intervals).keys()]
9
+ .slice(1)
10
+ .map((interval) => moment(sortedReadings[0].submittedAt).startOf(granularity).add(interval, granularity));
11
+ let interpolatedReadings = dates.map((date) => {
12
+ const reading = interpolate(sortedReadings, date, interpolationMethod);
13
+ return {
14
+ date,
15
+ reading
16
+ };
17
+ });
18
+ interpolatedReadings = interpolatedReadings.map((reading, index) => {
19
+ const prevReading = interpolatedReadings[index - 1];
20
+ if (prevReading) {
21
+ return {
22
+ ...reading,
23
+ consumption: reading.reading - prevReading.reading
24
+ };
25
+ }
26
+ return reading;
27
+ });
28
+ if (startDate) {
29
+ interpolatedReadings = interpolatedReadings.filter((reading) => moment(reading.date).isSameOrAfter(startDate, granularity));
30
+ }
31
+ if (endDate) {
32
+ interpolatedReadings = interpolatedReadings.filter((reading) => moment(reading.date).isSameOrBefore(endDate, granularity));
33
+ }
34
+ return interpolatedReadings;
35
+ };
36
+ const consumptionFromReadings = (readings, startDate, endDate, granularity = 'hours', interpolationMethod = 'linear') => {
37
+ // Split the readings into hourly readings and calculate the consumption for each hour
38
+ const apportioned = apportionedReadings(readings, granularity, interpolationMethod, startDate, endDate);
39
+ // Calculate the total consumption between the start and end dates
40
+ const totalConsumption = apportioned.reduce((acc, reading) => acc + reading.consumption, 0);
41
+ return { totalConsumption, apportioned };
42
+ };
43
+ export default {
44
+ apportionedReadings,
45
+ consumptionFromReadings
46
+ };
47
+ //# sourceMappingURL=consumption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumption.js","sourceRoot":"","sources":["src/consumption.ts"],"names":[],"mappings":"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"}
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import api from './api.js';
2
+ import consumption from './consumption.js';
3
+ import logger from './logger.js';
4
+ export { api, logger, consumption };
5
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAC3C,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,OAAO,EACL,GAAG,EACH,MAAM,EACN,WAAW,EACZ,CAAA"}
@@ -0,0 +1 @@
1
+ export declare function interpolate(meterReadings: any[], targetDate: any, method?: string): any;
@@ -0,0 +1,73 @@
1
+ import moment from 'moment';
2
+ export function interpolate(meterReadings, targetDate, method = 'linear') {
3
+ if (method === 'cubic') {
4
+ return cubicInterpolation(meterReadings, targetDate);
5
+ }
6
+ if (method === 'linear') {
7
+ return linearInterpolation(meterReadings, targetDate);
8
+ }
9
+ throw new Error(`Interpolation method ${method} not supported`);
10
+ }
11
+ function linearInterpolation(meterReadings, targetDate) {
12
+ // Ensure the meter readings are sorted by date
13
+ meterReadings.sort((a, b) => moment(a.submittedAt).unix() - moment(b.submittedAt).unix());
14
+ const targetTimestamp = moment(targetDate).startOf('minute').unix();
15
+ // Find the two nearest meter readings for the target date
16
+ let lowerIndex = -1;
17
+ let upperIndex = -1;
18
+ for (let i = 0; i < meterReadings.length; i++) {
19
+ const reading = meterReadings[i];
20
+ if (moment(reading.submittedAt).startOf('minute').unix() <= targetTimestamp) {
21
+ lowerIndex = i;
22
+ }
23
+ else {
24
+ upperIndex = i;
25
+ break;
26
+ }
27
+ }
28
+ // If no readings are available for interpolation, return null
29
+ if (lowerIndex === -1 || upperIndex === -1) {
30
+ return null;
31
+ }
32
+ const lowerReading = meterReadings[lowerIndex];
33
+ const upperReading = meterReadings[upperIndex];
34
+ // Linear interpolation formula
35
+ const consumption = lowerReading.value
36
+ + ((targetTimestamp - moment(lowerReading.submittedAt).startOf('minute').unix())
37
+ / (moment(upperReading.submittedAt).startOf('minute').unix() - moment(lowerReading.submittedAt).startOf('minute').unix()))
38
+ * (upperReading.value - lowerReading.value);
39
+ return consumption;
40
+ }
41
+ ;
42
+ function cubicInterpolateCalc(x0, y0, x1, y1, x2, y2, x3, y3, x) {
43
+ const t = (x - x1) / (x2 - x1);
44
+ const t2 = t * t;
45
+ const t3 = t2 * t;
46
+ const a0 = y3 - y2 - y0 + y1;
47
+ const a1 = y0 - y1 - a0;
48
+ const a2 = y2 - y0;
49
+ const a3 = y1;
50
+ return a0 * t3 + a1 * t2 + a2 * t + a3;
51
+ }
52
+ function cubicInterpolation(meterReadings, targetDate) {
53
+ // Ensure the meter readings are sorted by date
54
+ meterReadings.sort((a, b) => moment(a.submittedAt).unix() - moment(b.submittedAt).unix());
55
+ const targetTimestamp = new Date(targetDate).getTime();
56
+ // Find the four nearest meter readings for the target date
57
+ let startIndex = -1;
58
+ for (let i = 0; i < meterReadings.length; i++) {
59
+ const reading = meterReadings[i];
60
+ if (moment(reading.submittedAt).startOf('minute').unix() > targetTimestamp) {
61
+ startIndex = i - 2;
62
+ break;
63
+ }
64
+ }
65
+ // If no readings are available for interpolation, return null
66
+ if (startIndex < 0 || startIndex + 3 >= meterReadings.length) {
67
+ return null;
68
+ }
69
+ const readings = meterReadings.slice(startIndex, startIndex + 4);
70
+ const interpolatedConsumption = cubicInterpolateCalc(moment(readings[0].submittedAt).startOf('minute').unix(), readings[0].value, moment(readings[1].submittedAt).startOf('minute').unix(), readings[1].value, moment(readings[2].submittedAt).startOf('minute').unix(), readings[2].value, moment(readings[3].submittedAt).startOf('minute').unix(), readings[3].value, targetTimestamp);
71
+ return interpolatedConsumption;
72
+ }
73
+ //# sourceMappingURL=readingsInterpolate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readingsInterpolate.js","sourceRoot":"","sources":["../src/lib/readingsInterpolate.ts"],"names":[],"mappings":"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"}
package/logger.js ADDED
@@ -0,0 +1,10 @@
1
+ import winston from 'winston';
2
+ export default (namespace) => winston.createLogger({
3
+ level: 'debug',
4
+ format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
5
+ defaultMeta: { service: process.env.AWS_LAMBDA_FUNCTION_NAME, script: namespace },
6
+ transports: [
7
+ new winston.transports.Console()
8
+ ]
9
+ });
10
+ //# sourceMappingURL=logger.js.map
package/logger.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,eAAe,CAAC,SAAiB,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC;IACzD,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAC5B,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,EAC1B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CACtB;IACD,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,MAAM,EAAE,SAAS,EAAE;IACjF,UAAU,EAAE;QACV,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE;KACjC;CACF,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@etainabl/nodejs-sdk",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "author": "Jonathan Lambert <jonathan@etainabl.com>",
6
+ "type": "module",
7
+ "dependencies": {
8
+ "axios": "^1.4.0",
9
+ "moment": "^2.29.4",
10
+ "winston": "^3.10.0"
11
+ },
12
+ "devDependencies": {
13
+ "@types/node": "^20.4.5"
14
+ }
15
+ }
package/src/api.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type { AxiosRequestConfig, AxiosInstance, CreateAxiosDefaults } from 'axios';
2
+ interface ETNPagedResponse {
3
+ data: any[];
4
+ total: number;
5
+ limit: number;
6
+ skip: number;
7
+ }
8
+ interface AuthOptions {
9
+ key?: string;
10
+ token?: string;
11
+ }
12
+ declare const _default: (auth: AuthOptions, instanceOptions?: CreateAxiosDefaults) => {
13
+ instance: AxiosInstance;
14
+ getAccount: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
15
+ listAccounts: (options?: AxiosRequestConfig<any>) => Promise<ETNPagedResponse>;
16
+ updateAccount: (id: string, data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
17
+ createAccount: (data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
18
+ removeAccount: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
19
+ getAsset: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
20
+ listAssets: (options?: AxiosRequestConfig<any>) => Promise<ETNPagedResponse>;
21
+ updateAsset: (id: string, data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
22
+ createAsset: (data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
23
+ removeAsset: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
24
+ getAssetGroup: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
25
+ listAssetGroups: (options?: AxiosRequestConfig<any>) => Promise<ETNPagedResponse>;
26
+ updateAssetGroup: (id: string, data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
27
+ createAssetGroup: (data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
28
+ removeAssetGroup: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
29
+ getAssetGroupAssets: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
30
+ getAutomation: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
31
+ listAutomations: (options?: AxiosRequestConfig<any>) => Promise<ETNPagedResponse>;
32
+ updateAutomation: (id: string, data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
33
+ createAutomation: (data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
34
+ removeAutomation: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
35
+ getConsumption: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
36
+ listConsumptions: (options?: AxiosRequestConfig<any>) => Promise<ETNPagedResponse>;
37
+ updateConsumption: (id: string, data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
38
+ createConsumption: (data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
39
+ removeConsumption: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
40
+ getReading: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
41
+ listReadings: (options?: AxiosRequestConfig<any>) => Promise<ETNPagedResponse>;
42
+ updateReading: (id: string, data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
43
+ createReading: (data: any, options?: AxiosRequestConfig<any>) => Promise<any>;
44
+ removeReading: (id: string, options?: AxiosRequestConfig<any>) => Promise<any>;
45
+ };
46
+ export default _default;
package/src/api.ts ADDED
@@ -0,0 +1,212 @@
1
+ import axios from 'axios';
2
+ import type { AxiosRequestConfig, AxiosInstance, CreateAxiosDefaults } from 'axios';
3
+
4
+ import logger from './logger.js';
5
+
6
+ const log = logger('etainablApi');
7
+
8
+ interface ETNPagedResponse {
9
+ data: any[];
10
+ total: number;
11
+ limit: number;
12
+ skip: number;
13
+ }
14
+
15
+ const factory = {
16
+ get: (etainablApi: AxiosInstance, endpoint: any, postEndpoint?: any) => async (id: string, options: AxiosRequestConfig = {}) => {
17
+ log.info(`API Request: GET ${process.env.ETAINABL_API_URL}/${endpoint}/${id}${postEndpoint ? `/${postEndpoint}` : ''}`);
18
+
19
+ let response;
20
+
21
+ try {
22
+ response = await etainablApi.get(`${endpoint}/${id}${postEndpoint ? `/${postEndpoint}` : ''}`, options);
23
+ } catch (e: any) {
24
+ if (e.response?.data) throw new Error(`API error response: ${JSON.stringify(e.response.data)}`);
25
+ }
26
+
27
+ if (!response) {
28
+ throw new Error(`No response from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
29
+ }
30
+
31
+ if (response.status !== 200) {
32
+ throw new Error(`${response.status} ${response.statusText} response from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
33
+ }
34
+
35
+ if (!response.data) {
36
+ throw new Error(`No data from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
37
+ }
38
+
39
+ return response.data;
40
+ },
41
+ list: (etainablApi: AxiosInstance, endpoint: any) => async (options: AxiosRequestConfig = {}): Promise<ETNPagedResponse> => {
42
+ log.info(`API Request: GET ${process.env.ETAINABL_API_URL}/${endpoint}`);
43
+
44
+ let response;
45
+
46
+ try {
47
+ response = await etainablApi.get(`${endpoint}`, options);
48
+ } catch (e: any) {
49
+ if (e.response?.data) throw new Error(`API error response: ${JSON.stringify(e.response.data)}`);
50
+ }
51
+
52
+ if (!response) {
53
+ throw new Error(`No response from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
54
+ }
55
+
56
+ if (response.status !== 200) {
57
+ throw new Error(`${response.status} ${response.statusText} response from API (GET ${endpoint})`);
58
+ }
59
+
60
+ if (!response.data || !response.data.data) {
61
+ throw new Error(`No data from API (GET ${endpoint})`);
62
+ }
63
+
64
+ return response.data;
65
+ },
66
+ update: (etainablApi: AxiosInstance, endpoint: any) => async (id: string, data: any, options: AxiosRequestConfig = {}) => {
67
+ log.info(`API Request: PATCH ${process.env.ETAINABL_API_URL}/${endpoint}/${id}`);
68
+
69
+ let response;
70
+
71
+ try {
72
+ response = await etainablApi.patch(`${endpoint}/${id}`, data, options);
73
+ } catch (e: any) {
74
+ if (e.response?.data) throw new Error(`API error response: ${JSON.stringify(e.response.data)}`);
75
+ }
76
+
77
+ if (!response) {
78
+ throw new Error(`No response from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
79
+ }
80
+
81
+ if (response.status !== 200) {
82
+ throw new Error(`${response.status} ${response.statusText} response from API (PATCH ${endpoint})`);
83
+ }
84
+
85
+ if (!response.data) {
86
+ throw new Error(`No data from API (PATCH ${endpoint})`);
87
+ }
88
+
89
+ return response.data;
90
+ },
91
+ create: (etainablApi: AxiosInstance, endpoint: any) => async (data: any, options: AxiosRequestConfig = {}) => {
92
+ log.info(`API Request: POST ${process.env.ETAINABL_API_URL}/${endpoint}`);
93
+
94
+ let response;
95
+
96
+ try {
97
+ response = await etainablApi.post(`${endpoint}`, data, options);
98
+ } catch (e: any) {
99
+ if (e.response?.data) throw new Error(`API error response: ${JSON.stringify(e.response.data)}`);
100
+ }
101
+
102
+ if (!response) {
103
+ throw new Error(`No response from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
104
+ }
105
+
106
+ if (response.status !== 200) {
107
+ throw new Error(`${response.status} ${response.statusText} response from API (POST ${endpoint})`);
108
+ }
109
+
110
+ if (!response.data) {
111
+ throw new Error(`No data from API (POST ${endpoint})`);
112
+ }
113
+
114
+ return response.data;
115
+ },
116
+ remove: (etainablApi: AxiosInstance, endpoint: any) => async (id: string, options: AxiosRequestConfig = {}) => {
117
+ log.info(`API Request: DELETE ${process.env.ETAINABL_API_URL}/${endpoint}/${id}`);
118
+
119
+ let response;
120
+
121
+ try {
122
+ response = await etainablApi.delete(`${endpoint}/${id}`, options);
123
+ } catch (e: any) {
124
+ if (e.response?.data) throw new Error(`API error response: ${JSON.stringify(e.response.data)}`);
125
+ }
126
+
127
+ if (!response) {
128
+ throw new Error(`No response from API (GET ${endpoint}/:id${postEndpoint ? `/${postEndpoint}` : ''})`);
129
+ }
130
+
131
+ if (response.status !== 200) {
132
+ throw new Error(`${response.status} ${response.statusText} response from API (DELETE ${endpoint})`);
133
+ }
134
+
135
+ if (!response.data) {
136
+ throw new Error(`No data from API (DELETE ${endpoint})`);
137
+ }
138
+
139
+ return response.data;
140
+ }
141
+ };
142
+
143
+ interface AuthOptions {
144
+ key?: string;
145
+ token?: string;
146
+ }
147
+
148
+ export default (auth: AuthOptions, instanceOptions: CreateAxiosDefaults = {}) => {
149
+ const headers: any = {};
150
+
151
+ if (auth.key) {
152
+ headers['x-key'] = auth.key;
153
+ } else if (auth.token) {
154
+ headers['Authorization'] = auth.token;
155
+ } else {
156
+ headers['x-key'] = process.env.ETAINABL_API_KEY;
157
+ }
158
+
159
+ const etainablApi = axios.create({
160
+ baseURL: process.env.ETAINABL_API_URL,
161
+ timeout: 30000,
162
+ headers,
163
+ ...instanceOptions
164
+ });
165
+
166
+ return {
167
+ instance: etainablApi,
168
+
169
+ // accounts
170
+ getAccount: factory.get(etainablApi, 'accounts'),
171
+ listAccounts: factory.list(etainablApi, 'accounts'),
172
+ updateAccount: factory.update(etainablApi, 'accounts'),
173
+ createAccount: factory.create(etainablApi, 'accounts'),
174
+ removeAccount: factory.remove(etainablApi, 'accounts'),
175
+
176
+ // assets
177
+ getAsset: factory.get(etainablApi, 'assets'),
178
+ listAssets: factory.list(etainablApi, 'assets'),
179
+ updateAsset: factory.update(etainablApi, 'assets'),
180
+ createAsset: factory.create(etainablApi, 'assets'),
181
+ removeAsset: factory.remove(etainablApi, 'assets'),
182
+
183
+ // assetGroups
184
+ getAssetGroup: factory.get(etainablApi, 'asset-groups'),
185
+ listAssetGroups: factory.list(etainablApi, 'asset-groups'),
186
+ updateAssetGroup: factory.update(etainablApi, 'asset-groups'),
187
+ createAssetGroup: factory.create(etainablApi, 'asset-groups'),
188
+ removeAssetGroup: factory.remove(etainablApi, 'asset-groups'),
189
+ getAssetGroupAssets: factory.get(etainablApi, 'asset-groups', 'assets'),
190
+
191
+ // automation
192
+ getAutomation: factory.get(etainablApi, 'automation'),
193
+ listAutomations: factory.list(etainablApi, 'automation'),
194
+ updateAutomation: factory.update(etainablApi, 'automation'),
195
+ createAutomation: factory.create(etainablApi, 'automation'),
196
+ removeAutomation: factory.remove(etainablApi, 'automation'),
197
+
198
+ // consumption
199
+ getConsumption: factory.get(etainablApi, 'consumptions'),
200
+ listConsumptions: factory.list(etainablApi, 'consumptions'),
201
+ updateConsumption: factory.update(etainablApi, 'consumptions'),
202
+ createConsumption: factory.create(etainablApi, 'consumptions'),
203
+ removeConsumption: factory.remove(etainablApi, 'consumptions'),
204
+
205
+ // readings
206
+ getReading: factory.get(etainablApi, 'readings'),
207
+ listReadings: factory.list(etainablApi, 'readings'),
208
+ updateReading: factory.update(etainablApi, 'readings'),
209
+ createReading: factory.create(etainablApi, 'readings'),
210
+ removeReading: factory.remove(etainablApi, 'readings'),
211
+ }
212
+ };
@@ -0,0 +1,18 @@
1
+ import moment from 'moment';
2
+ export interface Reading {
3
+ submittedAt: string;
4
+ value: number;
5
+ }
6
+ export interface ApportionedReading {
7
+ date: moment.Moment;
8
+ reading: number;
9
+ consumption?: number;
10
+ }
11
+ declare const _default: {
12
+ apportionedReadings: (readings: Reading[], granularity?: moment.unitOfTime.Diff, interpolationMethod?: string, startDate?: string | undefined, endDate?: string | undefined) => ApportionedReading[];
13
+ consumptionFromReadings: (readings: Reading[], startDate: string, endDate: string, granularity?: moment.unitOfTime.Diff, interpolationMethod?: string) => {
14
+ totalConsumption: any;
15
+ apportioned: ApportionedReading[];
16
+ };
17
+ };
18
+ export default _default;
@@ -0,0 +1,73 @@
1
+ import moment from 'moment';
2
+ import { interpolate } from './lib/readingsInterpolate.js';
3
+
4
+ export interface Reading {
5
+ submittedAt: string,
6
+ value: number
7
+ }
8
+
9
+ export interface ApportionedReading {
10
+ date: moment.Moment,
11
+ reading: number,
12
+ consumption?: number
13
+ }
14
+
15
+ const apportionedReadings = (readings: Reading[], granularity: moment.unitOfTime.Diff = 'days', interpolationMethod: string = 'linear', startDate?: string, endDate?: string): ApportionedReading[] => {
16
+ // Readings sorted from newest to oldest
17
+ const sortedReadings = [...readings].sort((a, b) => moment(a.submittedAt).diff(b.submittedAt));
18
+
19
+ // Array of every hour/day etc between the first and last reading
20
+ const intervals = moment(sortedReadings[sortedReadings.length - 1].submittedAt).diff(moment(sortedReadings[0].submittedAt), granularity);
21
+
22
+ const dates = [...Array(intervals).keys()]
23
+ .slice(1)
24
+ .map((interval: number) => moment(sortedReadings[0].submittedAt).startOf(granularity).add(interval, granularity));
25
+
26
+ let interpolatedReadings = dates.map((date: moment.Moment) => {
27
+ const reading = interpolate(sortedReadings, date, interpolationMethod);
28
+
29
+ return {
30
+ date,
31
+ reading
32
+ };
33
+ });
34
+
35
+ interpolatedReadings = interpolatedReadings.map((reading: ApportionedReading, index: number) => {
36
+ const prevReading = interpolatedReadings[index - 1];
37
+
38
+ if (prevReading) {
39
+ return {
40
+ ...reading,
41
+ consumption: reading.reading - prevReading.reading
42
+ };
43
+ }
44
+
45
+ return reading;
46
+
47
+ });
48
+
49
+ if (startDate) {
50
+ interpolatedReadings = interpolatedReadings.filter((reading: ApportionedReading) => moment(reading.date).isSameOrAfter(startDate, granularity));
51
+ }
52
+
53
+ if (endDate) {
54
+ interpolatedReadings = interpolatedReadings.filter((reading: ApportionedReading) => moment(reading.date).isSameOrBefore(endDate, granularity));
55
+ }
56
+
57
+ return interpolatedReadings;
58
+ };
59
+
60
+ const consumptionFromReadings = (readings: Reading[], startDate: string, endDate: string, granularity: moment.unitOfTime.Diff = 'hours', interpolationMethod: string = 'linear') => {
61
+ // Split the readings into hourly readings and calculate the consumption for each hour
62
+ const apportioned = apportionedReadings(readings, granularity, interpolationMethod, startDate, endDate);
63
+
64
+ // Calculate the total consumption between the start and end dates
65
+ const totalConsumption = apportioned.reduce((acc: any, reading) => acc + reading.consumption, 0);
66
+
67
+ return { totalConsumption, apportioned };
68
+ };
69
+
70
+ export default {
71
+ apportionedReadings,
72
+ consumptionFromReadings
73
+ };
package/src/db.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { MongoClient, Db } from 'mongodb';
2
+ import { logger } from './logger';
3
+
4
+ const log = logger('dbHelpers');
5
+
6
+ let cachedDb: Db;
7
+
8
+ export async function connectToDatabase() {
9
+ log.debug('Connecting to MongoDB server...');
10
+
11
+ if (cachedDb) {
12
+ log.debug('Using cached MongoDB connection.');
13
+ return Promise.resolve(cachedDb);
14
+ }
15
+
16
+ const uri = `mongodb+srv://${process.env.ETAINABL_DB_URL}`;
17
+
18
+ const client = new MongoClient(uri, {
19
+ auth: {
20
+ username: process.env.AWS_ACCESS_KEY_ID,
21
+ password: process.env.AWS_SECRET_ACCESS_KEY
22
+ },
23
+ authSource: '$external',
24
+ authMechanism: 'MONGODB-AWS'
25
+ });
26
+
27
+ await client.connect();
28
+
29
+ log.debug('Connected successfully to MongoDB server!');
30
+
31
+ cachedDb = client.db('etainabl');
32
+
33
+ return cachedDb;
34
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import api from './api.js';
2
+ import consumption from './consumption.js';
3
+ import logger from './logger.js';
4
+ export { api, logger, consumption };
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ import api from './api.js';
2
+ import consumption from './consumption.js';
3
+ import logger from './logger.js';
4
+
5
+ export {
6
+ api,
7
+ logger,
8
+ consumption
9
+ }
@@ -0,0 +1,100 @@
1
+ import moment from 'moment';
2
+
3
+ export function interpolate(meterReadings: any[], targetDate: any, method: string = 'linear') {
4
+ if (method === 'cubic') {
5
+ return cubicInterpolation(meterReadings, targetDate);
6
+ }
7
+
8
+ if (method === 'linear') {
9
+ return linearInterpolation(meterReadings, targetDate);
10
+ }
11
+
12
+ throw new Error(`Interpolation method ${method} not supported`);
13
+ }
14
+
15
+ function linearInterpolation(meterReadings: any[], targetDate: any) {
16
+ // Ensure the meter readings are sorted by date
17
+ meterReadings.sort((a, b) => moment(a.submittedAt).unix() - moment(b.submittedAt).unix());
18
+
19
+ const targetTimestamp = moment(targetDate).startOf('minute').unix();
20
+
21
+ // Find the two nearest meter readings for the target date
22
+ let lowerIndex = -1;
23
+ let upperIndex = -1;
24
+ for (let i = 0; i < meterReadings.length; i++) {
25
+ const reading = meterReadings[i];
26
+ if (moment(reading.submittedAt).startOf('minute').unix() <= targetTimestamp) {
27
+ lowerIndex = i;
28
+ } else {
29
+ upperIndex = i;
30
+ break;
31
+ }
32
+ }
33
+
34
+ // If no readings are available for interpolation, return null
35
+ if (lowerIndex === -1 || upperIndex === -1) {
36
+ return null;
37
+ }
38
+
39
+ const lowerReading = meterReadings[lowerIndex];
40
+ const upperReading = meterReadings[upperIndex];
41
+
42
+ // Linear interpolation formula
43
+ const consumption = lowerReading.value
44
+ + ((targetTimestamp - moment(lowerReading.submittedAt).startOf('minute').unix())
45
+ / (moment(upperReading.submittedAt).startOf('minute').unix() - moment(lowerReading.submittedAt).startOf('minute').unix()))
46
+ * (upperReading.value - lowerReading.value);
47
+
48
+ return consumption;
49
+ };
50
+
51
+ function cubicInterpolateCalc(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x: number) {
52
+ const t = (x - x1) / (x2 - x1);
53
+ const t2 = t * t;
54
+ const t3 = t2 * t;
55
+
56
+ const a0 = y3 - y2 - y0 + y1;
57
+ const a1 = y0 - y1 - a0;
58
+ const a2 = y2 - y0;
59
+ const a3 = y1;
60
+
61
+ return a0 * t3 + a1 * t2 + a2 * t + a3;
62
+ }
63
+
64
+ function cubicInterpolation(meterReadings: any[], targetDate: any) {
65
+ // Ensure the meter readings are sorted by date
66
+ meterReadings.sort((a, b) => moment(a.submittedAt).unix() - moment(b.submittedAt).unix());
67
+
68
+ const targetTimestamp = new Date(targetDate).getTime();
69
+
70
+ // Find the four nearest meter readings for the target date
71
+ let startIndex = -1;
72
+ for (let i = 0; i < meterReadings.length; i++) {
73
+ const reading = meterReadings[i];
74
+ if (moment(reading.submittedAt).startOf('minute').unix() > targetTimestamp) {
75
+ startIndex = i - 2;
76
+ break;
77
+ }
78
+ }
79
+
80
+ // If no readings are available for interpolation, return null
81
+ if (startIndex < 0 || startIndex + 3 >= meterReadings.length) {
82
+ return null;
83
+ }
84
+
85
+ const readings = meterReadings.slice(startIndex, startIndex + 4);
86
+
87
+ const interpolatedConsumption = cubicInterpolateCalc(
88
+ moment(readings[0].submittedAt).startOf('minute').unix(),
89
+ readings[0].value,
90
+ moment(readings[1].submittedAt).startOf('minute').unix(),
91
+ readings[1].value,
92
+ moment(readings[2].submittedAt).startOf('minute').unix(),
93
+ readings[2].value,
94
+ moment(readings[3].submittedAt).startOf('minute').unix(),
95
+ readings[3].value,
96
+ targetTimestamp
97
+ );
98
+
99
+ return interpolatedConsumption;
100
+ }
@@ -0,0 +1,3 @@
1
+ import winston from 'winston';
2
+ declare const _default: (namespace: string) => winston.Logger;
3
+ export default _default;
package/src/logger.ts ADDED
@@ -0,0 +1,13 @@
1
+ import winston from 'winston';
2
+
3
+ export default (namespace: string) => winston.createLogger({
4
+ level: 'debug',
5
+ format: winston.format.combine(
6
+ winston.format.timestamp(),
7
+ winston.format.json()
8
+ ),
9
+ defaultMeta: { service: process.env.AWS_LAMBDA_FUNCTION_NAME, script: namespace },
10
+ transports: [
11
+ new winston.transports.Console()
12
+ ]
13
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "display": "soclean-connector",
3
+ "compilerOptions": {
4
+ "target": "es2022",
5
+ "strict": true,
6
+ "outDir": "./",
7
+ "rootDir": "./src",
8
+ "preserveConstEnums": true,
9
+ "sourceMap": true,
10
+ "module":"ESNext",
11
+ "moduleResolution":"NodeNext",
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "isolatedModules": true,
16
+ "pretty": true,
17
+ "resolveJsonModule": true,
18
+ "allowJs": true,
19
+ "declaration": true
20
+ },
21
+ "watchOptions": {
22
+ // Use native file system events for files and directories
23
+ "watchFile": "useFsEvents",
24
+ "watchDirectory": "useFsEvents",
25
+ // Poll files for updates more frequently
26
+ // when they're updated a lot.
27
+ "fallbackPolling": "dynamicPriority",
28
+ // Don't coalesce watch notification
29
+ "synchronousWatchDirectory": true
30
+ // Finally, two additional settings for reducing the amount of possible
31
+ // files to track work from these directories
32
+ },
33
+ "exclude": [],
34
+ "include": ["./src/*.ts"],
35
+ "compileOnSave": true
36
+ }