diff --git a/myems-api/MyEMS.postman_collection.json b/myems-api/MyEMS.postman_collection.json index e35172f1b8d61ce6f78d32dec886f25e1d5297b6..8d77e371ab620ab2c8a1c0fced349810696d8962 100644 --- a/myems-api/MyEMS.postman_collection.json +++ b/myems-api/MyEMS.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "58ea335d-8ee0-4af8-ae2e-9b9fdc6a5bd5", + "_postman_id": "7c7791de-7e38-4984-9c8b-64041f827207", "name": "MyEMS", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -26316,6 +26316,77 @@ }, "response": [] }, + { + "name": "Combined Equipment Plan Report Ⓔ", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/combinedequipmentplan?combinedequipmentid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "combinedequipmentplan" + ], + "query": [ + { + "key": "combinedequipmentid", + "value": "1", + "description": "use id or uuid" + }, + { + "key": "combinedequipmentuuid", + "value": "48aab70f-2e32-4518-9986-a6b7395acf58", + "description": "use id or uuid", + "disabled": true + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" + }, + { + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, { "name": "Combined Equipment Saving Report (Quick Mode) Ⓔ", "request": { @@ -28049,6 +28120,77 @@ }, "response": [] }, + { + "name": "Equipment Plan Report Ⓔ", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/equipmentplan?equipmentid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "equipmentplan" + ], + "query": [ + { + "key": "equipmentid", + "value": "1", + "description": "use id or uuid" + }, + { + "key": "equipmentuuid", + "value": "bfa8b106-89a1-49ca-9b2b-a481ac41a873", + "description": "use id or uuid", + "disabled": true + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" + }, + { + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, { "name": "Equipment Saving Report (Quick Mode) Ⓔ", "request": { @@ -29040,7 +29182,7 @@ "response": [] }, { - "name": "Meter Saving Report (Quick Mode) Ⓔ", + "name": "Meter Plan Report Ⓔ", "request": { "method": "GET", "header": [ @@ -29065,13 +29207,13 @@ } ], "url": { - "raw": "{{base_url}}/reports/metersaving?meterid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00&quickmode=true", + "raw": "{{base_url}}/reports/meterplan?meterid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", "host": [ "{{base_url}}" ], "path": [ "reports", - "metersaving" + "meterplan" ], "query": [ { @@ -29104,10 +29246,6 @@ { "key": "reportingperiodenddatetime", "value": "2020-10-01T00:00:00" - }, - { - "key": "quickmode", - "value": "true" } ] } @@ -29115,7 +29253,7 @@ "response": [] }, { - "name": "Meter Realtime Report", + "name": "Meter Saving Report (Quick Mode) Ⓔ", "request": { "method": "GET", "header": [ @@ -29140,13 +29278,13 @@ } ], "url": { - "raw": "{{base_url}}/reports/meterrealtime?meterid=1", + "raw": "{{base_url}}/reports/metersaving?meterid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00&quickmode=true", "host": [ "{{base_url}}" ], "path": [ "reports", - "meterrealtime" + "metersaving" ], "query": [ { @@ -29156,64 +29294,33 @@ }, { "key": "meteruuid", - "value": "eb78f7f9-f26f-463b-92fa-d9daf5b3651c", + "value": "5ca47bc5-22c2-47fc-b906-33222191ea40", "description": "use meterid or meteruuid", "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "Meter Realtime Report (Quick Mode)", - "request": { - "method": "GET", - "header": [ - { - "key": "User-UUID", - "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", - "type": "text", - "description": "Any admin users' UUID" - }, - { - "key": "Token", - "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", - "type": "text", - "description": "Login first to get a valid token" - }, - { - "key": "API-Key", - "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", - "type": "default", - "description": "Create API Key at myems-admin", - "disabled": true - } - ], - "url": { - "raw": "{{base_url}}/reports/meterrealtime?meterid=1&quickmode=true", - "host": [ - "{{base_url}}" - ], - "path": [ - "reports", - "meterrealtime" - ], - "query": [ + }, { - "key": "meterid", - "value": "1", - "description": "use meterid or meteruuid" + "key": "periodtype", + "value": "daily" }, { - "key": "quickmode", - "value": "true" + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" }, { - "key": "meteruuid", - "value": "eb78f7f9-f26f-463b-92fa-d9daf5b3651c", - "description": "use meterid or meteruuid", - "disabled": true + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + }, + { + "key": "quickmode", + "value": "true" } ] } @@ -29221,7 +29328,7 @@ "response": [] }, { - "name": "Meter Submeters Balance Report", + "name": "Meter Realtime Report", "request": { "method": "GET", "header": [ @@ -29246,13 +29353,13 @@ } ], "url": { - "raw": "{{base_url}}/reports/metersubmetersbalance?meterid=1&periodtype=daily&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "raw": "{{base_url}}/reports/meterrealtime?meterid=1", "host": [ "{{base_url}}" ], "path": [ "reports", - "metersubmetersbalance" + "meterrealtime" ], "query": [ { @@ -29265,18 +29372,6 @@ "value": "eb78f7f9-f26f-463b-92fa-d9daf5b3651c", "description": "use meterid or meteruuid", "disabled": true - }, - { - "key": "periodtype", - "value": "daily" - }, - { - "key": "reportingperiodstartdatetime", - "value": "2020-09-01T00:00:00" - }, - { - "key": "reportingperiodenddatetime", - "value": "2020-10-01T00:00:00" } ] } @@ -29284,7 +29379,7 @@ "response": [] }, { - "name": "Meter Submeters Balance Report (Quick Mode)", + "name": "Meter Realtime Report (Quick Mode)", "request": { "method": "GET", "header": [ @@ -29309,13 +29404,13 @@ } ], "url": { - "raw": "{{base_url}}/reports/metersubmetersbalance?meterid=1&periodtype=daily&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00&quickmode=true", + "raw": "{{base_url}}/reports/meterrealtime?meterid=1&quickmode=true", "host": [ "{{base_url}}" ], "path": [ "reports", - "metersubmetersbalance" + "meterrealtime" ], "query": [ { @@ -29323,18 +29418,6 @@ "value": "1", "description": "use meterid or meteruuid" }, - { - "key": "periodtype", - "value": "daily" - }, - { - "key": "reportingperiodstartdatetime", - "value": "2020-09-01T00:00:00" - }, - { - "key": "reportingperiodenddatetime", - "value": "2020-10-01T00:00:00" - }, { "key": "quickmode", "value": "true" @@ -29351,7 +29434,137 @@ "response": [] }, { - "name": "Meter Trend Report", + "name": "Meter Submeters Balance Report", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/metersubmetersbalance?meterid=1&periodtype=daily&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "metersubmetersbalance" + ], + "query": [ + { + "key": "meterid", + "value": "1", + "description": "use meterid or meteruuid" + }, + { + "key": "meteruuid", + "value": "eb78f7f9-f26f-463b-92fa-d9daf5b3651c", + "description": "use meterid or meteruuid", + "disabled": true + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, + { + "name": "Meter Submeters Balance Report (Quick Mode)", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/metersubmetersbalance?meterid=1&periodtype=daily&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00&quickmode=true", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "metersubmetersbalance" + ], + "query": [ + { + "key": "meterid", + "value": "1", + "description": "use meterid or meteruuid" + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + }, + { + "key": "quickmode", + "value": "true" + }, + { + "key": "meteruuid", + "value": "eb78f7f9-f26f-463b-92fa-d9daf5b3651c", + "description": "use meterid or meteruuid", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Meter Trend Report", "request": { "method": "GET", "header": [ @@ -30307,6 +30520,70 @@ }, "response": [] }, + { + "name": "Offline Meter Plan Report Ⓔ", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/offlinemeterplan?offlinemeterid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "offlinemeterplan" + ], + "query": [ + { + "key": "offlinemeterid", + "value": "1" + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" + }, + { + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, { "name": "Offline Meter Saving Report (Quick Mode) Ⓔ", "request": { @@ -31240,6 +31517,77 @@ }, "response": [] }, + { + "name": "Shopfloor Plan Report Ⓔ", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/shopfloorplan?shopfloorid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "shopfloorplan" + ], + "query": [ + { + "key": "shopfloorid", + "value": "1", + "description": "use id or uuid" + }, + { + "key": "shopflooruuid", + "value": "d03837fd-9d30-44fe-9443-154f7c7e15f1", + "description": "use id or uuid", + "disabled": true + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" + }, + { + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, { "name": "Shopfloor Saving Report (Quick Mode) Ⓔ", "request": { @@ -33673,6 +34021,77 @@ }, "response": [] }, + { + "name": "Store Plan Report Ⓔ", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/storeplan?storeid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "storeplan" + ], + "query": [ + { + "key": "storeid", + "value": "1", + "description": "use id or uuid" + }, + { + "key": "storeuuid", + "value": "d8a24322-4bab-4ba2-aedc-5d55a84c3db8", + "description": "use id or uuid", + "disabled": true + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" + }, + { + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, { "name": "Store Saving Report (Quick Mode) Ⓔ", "request": { @@ -34780,6 +35199,77 @@ }, "response": [] }, + { + "name": "Tenant Plan Report Ⓔ", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/tenantplan?tenantid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "tenantplan" + ], + "query": [ + { + "key": "tenantid", + "value": "1", + "description": "use id or uuid" + }, + { + "key": "tenantuuid", + "value": "6b0da806-a4cd-431a-8116-2915e90aaf8b", + "description": "use id or uuid", + "disabled": true + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" + }, + { + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, { "name": "Tenant Saving Report (Quick Mode) Ⓔ", "request": { @@ -35569,6 +36059,70 @@ }, "response": [] }, + { + "name": "Virtual Meter Plan Report", + "request": { + "method": "GET", + "header": [ + { + "key": "User-UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text", + "description": "Any admin users' UUID" + }, + { + "key": "Token", + "value": "c5f872b51ed40ccf55f1c1d6dbd8cb86eefba5d1010e23b2386bd82be431f3eafc0e3360dee18a5327d9e9852e3cf7caad3b81e252f9f311790c22f7a62a90e1", + "type": "text", + "description": "Login first to get a valid token" + }, + { + "key": "API-Key", + "value": "c5ee62be2792ed4a59de1096511934288f4c192363529dafc00b3b35f81f224a5cc44c9aae46ac8966dc52f1ea0039395551bdf3f86aff6bb2b6b032834fc139", + "type": "default", + "description": "Create API Key at myems-admin", + "disabled": true + } + ], + "url": { + "raw": "{{base_url}}/reports/virtualmeterplan?virtualmeterid=1&periodtype=daily&baseperiodstartdatetime=2020-08-01T00:00:00&baseperiodenddatetime=2020-09-01T00:00:00&reportingperiodstartdatetime=2020-09-01T00:00:00&reportingperiodenddatetime=2020-10-01T00:00:00", + "host": [ + "{{base_url}}" + ], + "path": [ + "reports", + "virtualmeterplan" + ], + "query": [ + { + "key": "virtualmeterid", + "value": "1" + }, + { + "key": "periodtype", + "value": "daily" + }, + { + "key": "baseperiodstartdatetime", + "value": "2020-08-01T00:00:00" + }, + { + "key": "baseperiodenddatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodstartdatetime", + "value": "2020-09-01T00:00:00" + }, + { + "key": "reportingperiodenddatetime", + "value": "2020-10-01T00:00:00" + } + ] + } + }, + "response": [] + }, { "name": "Virtual Meter Saving Report (Quick Mode)", "request": { diff --git a/myems-api/app.py b/myems-api/app.py index e7ec397f8741897c5e09bdeadd8e68ac9a06d929..a32b495cdcb4c7c229d1233486f45d60cf194688 100644 --- a/myems-api/app.py +++ b/myems-api/app.py @@ -20,6 +20,7 @@ from reports import combinedequipmentenergyitem from reports import combinedequipmentincome from reports import combinedequipmentload from reports import combinedequipmentoutput +from reports import combinedequipmentplan from reports import combinedequipmentsaving from reports import combinedequipmentstatistics from reports import dashboard @@ -34,6 +35,7 @@ from reports import equipmentenergyitem from reports import equipmentincome from reports import equipmentload from reports import equipmentoutput +from reports import equipmentplan from reports import equipmentsaving from reports import equipmentstatistics from reports import equipmenttracking @@ -48,6 +50,7 @@ from reports import metercomparison from reports import metercost from reports import meterenergy from reports import meterrealtime +from reports import meterplan from reports import metersaving from reports import metersubmetersbalance from reports import metertracking @@ -60,6 +63,7 @@ from reports import offlinemeterbatch from reports import offlinemetercarbon from reports import offlinemetercost from reports import offlinemeterenergy +from reports import offlinemeterplan from reports import offlinemetersaving from reports import offlinemeterdaily from reports import offlinemeterinput @@ -70,6 +74,7 @@ from reports import shopfloordashboard from reports import shopfloorenergycategory from reports import shopfloorenergyitem from reports import shopfloorload +from reports import shopfloorplan from reports import shopfloorsaving from reports import shopfloorstatistics from reports import spacecarbon @@ -92,6 +97,7 @@ from reports import storeenergycategory from reports import storeenergyitem from reports import storeload from reports import storesaving +from reports import storeplan from reports import storestatistics from reports import pointrealtime from reports import tenantbatch @@ -103,12 +109,14 @@ from reports import tenantenergycategory from reports import tenantenergyitem from reports import tenantload from reports import tenantsaving +from reports import tenantplan from reports import tenantstatistics from reports import virtualmeterbatch from reports import virtualmetercarbon from reports import virtualmetercost from reports import virtualmeterenergy from reports import virtualmetersaving +from reports import virtualmeterplan ######################################################################################################################## # BEGIN imports for Enterprise Version @@ -909,6 +917,8 @@ api.add_route('/reports/combinedequipmentoutput', combinedequipmentoutput.Reporting()) api.add_route('/reports/combinedequipmentsaving', combinedequipmentsaving.Reporting()) +api.add_route('/reports/combinedequipmentplan', + combinedequipmentplan.Reporting()) api.add_route('/reports/combinedequipmentstatistics', combinedequipmentstatistics.Reporting()) api.add_route('/reports/dashboard', @@ -941,6 +951,8 @@ api.add_route('/reports/equipmentoutput', equipmentoutput.Reporting()) api.add_route('/reports/equipmentsaving', equipmentsaving.Reporting()) +api.add_route('/reports/equipmentplan', + equipmentplan.Reporting()) api.add_route('/reports/equipmentstatistics', equipmentstatistics.Reporting()) api.add_route('/reports/equipmenttracking', @@ -961,6 +973,8 @@ api.add_route('/reports/meterrealtime', meterrealtime.Reporting()) api.add_route('/reports/metersaving', metersaving.Reporting()) +api.add_route('/reports/meterplan', + meterplan.Reporting()) api.add_route('/reports/metersubmetersbalance', metersubmetersbalance.Reporting()) api.add_route('/reports/metertrend', @@ -989,6 +1003,8 @@ api.add_route('/reports/offlinemeterinput', offlinemeterinput.Reporting()) api.add_route('/reports/offlinemetersaving', offlinemetersaving.Reporting()) +api.add_route('/reports/offlinemeterplan', + offlinemeterplan.Reporting()) api.add_route('/reports/pointrealtime', pointrealtime.Reporting()) api.add_route('/reports/shopfloorcarbon', @@ -1005,6 +1021,8 @@ api.add_route('/reports/shopfloorload', shopfloorload.Reporting()) api.add_route('/reports/shopfloorsaving', shopfloorsaving.Reporting()) +api.add_route('/reports/shopfloorplan', + shopfloorplan.Reporting()) api.add_route('/reports/shopfloorstatistics', shopfloorstatistics.Reporting()) api.add_route('/reports/shopfloorbatch', @@ -1049,6 +1067,8 @@ api.add_route('/reports/storeload', storeload.Reporting()) api.add_route('/reports/storesaving', storesaving.Reporting()) +api.add_route('/reports/storeplan', + storeplan.Reporting()) api.add_route('/reports/storestatistics', storestatistics.Reporting()) api.add_route('/reports/tenantbatch', @@ -1069,12 +1089,16 @@ api.add_route('/reports/tenantload', tenantload.Reporting()) api.add_route('/reports/tenantsaving', tenantsaving.Reporting()) +api.add_route('/reports/tenantplan', + tenantplan.Reporting()) api.add_route('/reports/tenantstatistics', tenantstatistics.Reporting()) api.add_route('/reports/virtualmeterbatch', virtualmeterbatch.Reporting()) api.add_route('/reports/virtualmetersaving', virtualmetersaving.Reporting()) +api.add_route('/reports/virtualmeterplan', + virtualmeterplan.Reporting()) api.add_route('/reports/virtualmeterenergy', virtualmeterenergy.Reporting()) api.add_route('/reports/virtualmetercarbon', diff --git a/myems-api/reports/combinedequipmentplan.py b/myems-api/reports/combinedequipmentplan.py new file mode 100644 index 0000000000000000000000000000000000000000..7db68fc4089ce97b3cad0e0dcdb97c194281619c --- /dev/null +++ b/myems-api/reports/combinedequipmentplan.py @@ -0,0 +1,862 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json +import config +import excelexporters.combinedequipmentsaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """"Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the combined equipment + # Step 3: query energy categories + # Step 4: query associated points + # Step 5: query associated equipments + # Step 6: query base period energy saving + # Step 7: query reporting period energy saving + # Step 8: query tariff data + # Step 9: query associated points data + # Step 10: query associated equipments energy saving + # Step 11: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + combined_equipment_id = req.params.get('combinedequipmentid') + combined_equipment_uuid = req.params.get('combinedequipmentuuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if combined_equipment_id is None and combined_equipment_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_COMBINED_EQUIPMENT_ID') + + if combined_equipment_id is not None: + combined_equipment_id = str.strip(combined_equipment_id) + if not combined_equipment_id.isdigit() or int(combined_equipment_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_COMBINED_EQUIPMENT_ID') + + if combined_equipment_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(combined_equipment_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_COMBINED_EQUIPMENT_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the combined equipment + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() + + if combined_equipment_id is not None: + cursor_system.execute(" SELECT id, name, cost_center_id " + " FROM tbl_combined_equipments " + " WHERE id = %s ", (combined_equipment_id,)) + row_combined_equipment = cursor_system.fetchone() + elif combined_equipment_uuid is not None: + cursor_system.execute(" SELECT id, name, cost_center_id " + " FROM tbl_combined_equipments " + " WHERE uuid = %s ", (combined_equipment_uuid,)) + row_combined_equipment = cursor_system.fetchone() + + if row_combined_equipment is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, + title='API.NOT_FOUND', + description='API.COMBINED_EQUIPMENT_NOT_FOUND') + + combined_equipment = dict() + combined_equipment['id'] = row_combined_equipment[0] + combined_equipment['name'] = row_combined_equipment[1] + combined_equipment['cost_center_id'] = row_combined_equipment[2] + + ################################################################################################################ + # Step 3: query energy categories + ################################################################################################################ + energy_category_set = set() + # query energy categories in base period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_combined_equipment_input_category_hourly " + " WHERE combined_equipment_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (combined_equipment['id'], base_start_datetime_utc, base_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query energy categories in reporting period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_combined_equipment_input_category_hourly " + " WHERE combined_equipment_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (combined_equipment['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query all energy categories in base period and reporting period + cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e " + " FROM tbl_energy_categories " + " ORDER BY id ", ) + rows_energy_categories = cursor_system.fetchall() + if rows_energy_categories is None or len(rows_energy_categories) == 0: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, + title='API.NOT_FOUND', + description='API.ENERGY_CATEGORY_NOT_FOUND') + energy_category_dict = dict() + for row_energy_category in rows_energy_categories: + if row_energy_category[0] in energy_category_set: + energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1], + "unit_of_measure": row_energy_category[2], + "kgce": row_energy_category[3], + "kgco2e": row_energy_category[4]} + + ################################################################################################################ + # Step 4: query associated points + ################################################################################################################ + point_list = list() + cursor_system.execute(" SELECT p.id, ep.name, p.units, p.object_type " + " FROM tbl_combined_equipments e, tbl_combined_equipments_parameters ep, tbl_points p " + " WHERE e.id = %s AND e.id = ep.combined_equipment_id AND ep.parameter_type = 'point' " + " AND ep.point_id = p.id " + " ORDER BY p.id ", (combined_equipment['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 5: query associated equipments + ################################################################################################################ + associated_equipment_list = list() + cursor_system.execute(" SELECT e.id, e.name " + " FROM tbl_equipments e,tbl_combined_equipments_equipments ee" + " WHERE ee.combined_equipment_id = %s AND e.id = ee.equipment_id" + " ORDER BY id ", (combined_equipment['id'],)) + rows_associated_equipments = cursor_system.fetchall() + if rows_associated_equipments is not None and len(rows_associated_equipments) > 0: + for row in rows_associated_equipments: + associated_equipment_list.append({"id": row[0], "name": row[1]}) + + ################################################################################################################ + # Step 6: query base period energy saving + ################################################################################################################ + base = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + base[energy_category_id] = dict() + base[energy_category_id]['timestamps'] = list() + base[energy_category_id]['values_plan'] = list() + base[energy_category_id]['values_actual'] = list() + base[energy_category_id]['values_saving'] = list() + base[energy_category_id]['subtotal_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query base period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_combined_equipment_input_category_hourly " + " WHERE combined_equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (combined_equipment['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_combined_equipment_hourly = cursor_energy_plan.fetchall() + + rows_combined_equipment_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_combined_equipment_periodically in rows_combined_equipment_periodically: + current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \ + else row_combined_equipment_periodically[1] + base[energy_category_id]['timestamps'].append(current_datetime) + base[energy_category_id]['values_plan'].append(plan_value) + base[energy_category_id]['subtotal_plan'] += plan_value + base[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query base period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_combined_equipment_input_category_hourly " + " WHERE combined_equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (combined_equipment['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_combined_equipment_hourly = cursor_energy.fetchall() + + rows_combined_equipment_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_combined_equipment_periodically in rows_combined_equipment_periodically: + current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \ + else row_combined_equipment_periodically[1] + base[energy_category_id]['values_actual'].append(actual_value) + base[energy_category_id]['subtotal_actual'] += actual_value + base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate base period's energy savings + for i in range(len(base[energy_category_id]['values_plan'])): + base[energy_category_id]['values_saving'].append( + base[energy_category_id]['values_plan'][i] - + base[energy_category_id]['values_actual'][i]) + + base[energy_category_id]['subtotal_saving'] = \ + base[energy_category_id]['subtotal_plan'] - \ + base[energy_category_id]['subtotal_actual'] + base[energy_category_id]['subtotal_in_kgce_saving'] = \ + base[energy_category_id]['subtotal_in_kgce_plan'] - \ + base[energy_category_id]['subtotal_in_kgce_actual'] + base[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + base[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + base[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 7: query reporting period energy saving + ################################################################################################################ + reporting = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + reporting[energy_category_id] = dict() + reporting[energy_category_id]['timestamps'] = list() + reporting[energy_category_id]['values_plan'] = list() + reporting[energy_category_id]['values_actual'] = list() + reporting[energy_category_id]['values_saving'] = list() + reporting[energy_category_id]['subtotal_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query reporting period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_combined_equipment_input_category_hourly " + " WHERE combined_equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (combined_equipment['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_combined_equipment_hourly = cursor_energy_plan.fetchall() + + rows_combined_equipment_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_combined_equipment_periodically in rows_combined_equipment_periodically: + current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \ + else row_combined_equipment_periodically[1] + reporting[energy_category_id]['timestamps'].append(current_datetime) + reporting[energy_category_id]['values_plan'].append(plan_value) + reporting[energy_category_id]['subtotal_plan'] += plan_value + reporting[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query reporting period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_combined_equipment_input_category_hourly " + " WHERE combined_equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (combined_equipment['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_combined_equipment_hourly = cursor_energy.fetchall() + + rows_combined_equipment_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_combined_equipment_periodically in rows_combined_equipment_periodically: + current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \ + else row_combined_equipment_periodically[1] + reporting[energy_category_id]['values_actual'].append(actual_value) + reporting[energy_category_id]['subtotal_actual'] += actual_value + reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate reporting period's energy savings + for i in range(len(reporting[energy_category_id]['values_plan'])): + reporting[energy_category_id]['values_saving'].append( + reporting[energy_category_id]['values_plan'][i] - + reporting[energy_category_id]['values_actual'][i]) + + reporting[energy_category_id]['subtotal_saving'] = \ + reporting[energy_category_id]['subtotal_plan'] - \ + reporting[energy_category_id]['subtotal_actual'] + reporting[energy_category_id]['subtotal_in_kgce_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgce_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgce_actual'] + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 8: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if not is_quick_mode: + if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + energy_category_tariff_dict = \ + utilities.get_energy_category_tariffs(combined_equipment['cost_center_id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in energy_category_tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19][0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append( + _('Tariff') + '-' + energy_category_dict[energy_category_id]['name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 9: query associated points data + ################################################################################################################ + if not is_quick_mode: + for point in point_list: + point_values = [] + point_timestamps = [] + if point['object_type'] == 'ENERGY_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_energy_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'ANALOG_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_analog_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'DIGITAL_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_digital_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + + parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')') + parameters_data['timestamps'].append(point_timestamps) + parameters_data['values'].append(point_values) + + ################################################################################################################ + # Step 10: query associated equipments energy saving + ################################################################################################################ + associated_equipment_data = dict() + + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + associated_equipment_data[energy_category_id] = dict() + associated_equipment_data[energy_category_id]['associated_equipment_names'] = list() + associated_equipment_data[energy_category_id]['subtotal_saving'] = list() + + for associated_equipment in associated_equipment_list: + subtotal_plan = Decimal(0.0) + subtotal_actual = Decimal(0.0) + associated_equipment_data[energy_category_id]['associated_equipment_names'].append( + associated_equipment['name']) + + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (associated_equipment['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_associated_equipment_hourly = cursor_energy_plan.fetchall() + + rows_associated_equipment_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_associated_equipment_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_associated_equipment_periodically in rows_associated_equipment_periodically: + plan_value = Decimal(0.0) if row_associated_equipment_periodically[1] is None \ + else row_associated_equipment_periodically[1] + subtotal_plan += plan_value + + # query reporting period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (associated_equipment['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_associated_equipment_hourly = cursor_energy.fetchall() + + rows_associated_equipment_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_associated_equipment_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_associated_equipment_periodically in rows_associated_equipment_periodically: + actual_value = Decimal(0.0) if row_associated_equipment_periodically[1] is None \ + else row_associated_equipment_periodically[1] + subtotal_actual += actual_value + + associated_equipment_data[energy_category_id]['subtotal_saving'].append( + subtotal_plan - subtotal_actual) + + ################################################################################################################ + # Step 11: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + + result = dict() + + result['combined_equipment'] = dict() + result['combined_equipment']['name'] = combined_equipment['name'] + + result['base_period'] = dict() + result['base_period']['names'] = list() + result['base_period']['units'] = list() + result['base_period']['timestamps'] = list() + result['base_period']['values_saving'] = list() + result['base_period']['subtotals_saving'] = list() + result['base_period']['subtotals_in_kgce_saving'] = list() + result['base_period']['subtotals_in_kgco2e_saving'] = list() + result['base_period']['total_in_kgce_saving'] = Decimal(0.0) + result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0) + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['base_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['base_period']['timestamps'].append(base[energy_category_id]['timestamps']) + result['base_period']['values_saving'].append(base[energy_category_id]['values_saving']) + result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving']) + result['base_period']['subtotals_in_kgce_saving'].append( + base[energy_category_id]['subtotal_in_kgce_saving']) + result['base_period']['subtotals_in_kgco2e_saving'].append( + base[energy_category_id]['subtotal_in_kgco2e_saving']) + result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving'] + result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving'] + + result['reporting_period'] = dict() + result['reporting_period']['names'] = list() + result['reporting_period']['energy_category_ids'] = list() + result['reporting_period']['units'] = list() + result['reporting_period']['timestamps'] = list() + result['reporting_period']['values_saving'] = list() + result['reporting_period']['rates_saving'] = list() + result['reporting_period']['subtotals_saving'] = list() + result['reporting_period']['subtotals_in_kgce_saving'] = list() + result['reporting_period']['subtotals_in_kgco2e_saving'] = list() + result['reporting_period']['increment_rates_saving'] = list() + result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0) + + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['reporting_period']['energy_category_ids'].append(energy_category_id) + result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps']) + result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving']) + result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving']) + result['reporting_period']['subtotals_in_kgce_saving'].append( + reporting[energy_category_id]['subtotal_in_kgce_saving']) + result['reporting_period']['subtotals_in_kgco2e_saving'].append( + reporting[energy_category_id]['subtotal_in_kgco2e_saving']) + result['reporting_period']['increment_rates_saving'].append( + (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) / + base[energy_category_id]['subtotal_saving'] + if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None) + result['reporting_period']['total_in_kgce_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgce_saving'] + result['reporting_period']['total_in_kgco2e_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] + + rate = list() + for index, value in enumerate(reporting[energy_category_id]['values_saving']): + if index < len(base[energy_category_id]['values_saving']) \ + and base[energy_category_id]['values_saving'][index] != 0 and value != 0: + rate.append((value - base[energy_category_id]['values_saving'][index]) + / base[energy_category_id]['values_saving'][index]) + else: + rate.append(None) + result['reporting_period']['rates_saving'].append(rate) + + result['reporting_period']['increment_rate_in_kgce_saving'] = \ + (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \ + result['base_period']['total_in_kgce_saving'] \ + if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None + + result['reporting_period']['increment_rate_in_kgco2e_saving'] = \ + (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \ + result['base_period']['total_in_kgco2e_saving'] \ + if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None + + result['parameters'] = { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + } + + result['associated_equipment'] = dict() + result['associated_equipment']['energy_category_names'] = list() + result['associated_equipment']['units'] = list() + result['associated_equipment']['associated_equipment_names_array'] = list() + result['associated_equipment']['subtotals_saving_array'] = list() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['associated_equipment']['energy_category_names'].append( + energy_category_dict[energy_category_id]['name']) + result['associated_equipment']['units'].append( + energy_category_dict[energy_category_id]['unit_of_measure']) + result['associated_equipment']['associated_equipment_names_array'].append( + associated_equipment_data[energy_category_id]['associated_equipment_names']) + result['associated_equipment']['subtotals_saving_array'].append( + associated_equipment_data[energy_category_id]['subtotal_saving']) + + # export result to Excel file and then encode the file to base64 string + result['excel_bytes_base64'] = None + if not is_quick_mode: + result['excel_bytes_base64'] = \ + excelexporters.combinedequipmentsaving.export(result, + combined_equipment['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result) diff --git a/myems-api/reports/equipmentplan.py b/myems-api/reports/equipmentplan.py new file mode 100644 index 0000000000000000000000000000000000000000..ae7be6865a8c500cf5a58d91b3e4d5e73ce2488d --- /dev/null +++ b/myems-api/reports/equipmentplan.py @@ -0,0 +1,754 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json +import config +import excelexporters.equipmentsaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """"Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the equipment + # Step 3: query energy categories + # Step 4: query associated points + # Step 5: query base period energy saving + # Step 6: query reporting period energy saving + # Step 7: query tariff data + # Step 8: query associated points data + # Step 10: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + equipment_id = req.params.get('equipmentid') + equipment_uuid = req.params.get('equipmentuuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if equipment_id is None and equipment_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_EQUIPMENT_ID') + + if equipment_id is not None: + equipment_id = str.strip(equipment_id) + if not equipment_id.isdigit() or int(equipment_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_EQUIPMENT_ID') + + if equipment_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(equipment_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_EQUIPMENT_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the equipment + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() + + if equipment_id is not None: + cursor_system.execute(" SELECT id, name, cost_center_id " + " FROM tbl_equipments " + " WHERE id = %s ", (equipment_id,)) + row_equipment = cursor_system.fetchone() + elif equipment_uuid is not None: + cursor_system.execute(" SELECT id, name, cost_center_id " + " FROM tbl_equipments " + " WHERE uuid = %s ", (equipment_uuid,)) + row_equipment = cursor_system.fetchone() + + if row_equipment is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.EQUIPMENT_NOT_FOUND') + + equipment = dict() + equipment['id'] = row_equipment[0] + equipment['name'] = row_equipment[1] + equipment['cost_center_id'] = row_equipment[2] + + ################################################################################################################ + # Step 3: query energy categories + ################################################################################################################ + energy_category_set = set() + # query energy categories in base period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (equipment['id'], base_start_datetime_utc, base_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query energy categories in reporting period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (equipment['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query all energy categories in base period and reporting period + cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e " + " FROM tbl_energy_categories " + " ORDER BY id ", ) + rows_energy_categories = cursor_system.fetchall() + if rows_energy_categories is None or len(rows_energy_categories) == 0: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, + title='API.NOT_FOUND', + description='API.ENERGY_CATEGORY_NOT_FOUND') + energy_category_dict = dict() + for row_energy_category in rows_energy_categories: + if row_energy_category[0] in energy_category_set: + energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1], + "unit_of_measure": row_energy_category[2], + "kgce": row_energy_category[3], + "kgco2e": row_energy_category[4]} + + ################################################################################################################ + # Step 4: query associated points + ################################################################################################################ + point_list = list() + cursor_system.execute(" SELECT p.id, ep.name, p.units, p.object_type " + " FROM tbl_equipments e, tbl_equipments_parameters ep, tbl_points p " + " WHERE e.id = %s AND e.id = ep.equipment_id AND ep.parameter_type = 'point' " + " AND ep.point_id = p.id " + " ORDER BY p.id ", (equipment['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 5: query base period energy saving + ################################################################################################################ + base = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + base[energy_category_id] = dict() + base[energy_category_id]['timestamps'] = list() + base[energy_category_id]['values_plan'] = list() + base[energy_category_id]['values_actual'] = list() + base[energy_category_id]['values_saving'] = list() + base[energy_category_id]['subtotal_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query base period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (equipment['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_equipment_hourly = cursor_energy_plan.fetchall() + + rows_equipment_periodically = utilities.aggregate_hourly_data_by_period(rows_equipment_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_equipment_periodically in rows_equipment_periodically: + current_datetime_local = row_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_equipment_periodically[1] is None \ + else row_equipment_periodically[1] + base[energy_category_id]['timestamps'].append(current_datetime) + base[energy_category_id]['values_plan'].append(plan_value) + base[energy_category_id]['subtotal_plan'] += plan_value + base[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query base period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (equipment['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_equipment_hourly = cursor_energy.fetchall() + + rows_equipment_periodically = utilities.aggregate_hourly_data_by_period(rows_equipment_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_equipment_periodically in rows_equipment_periodically: + current_datetime_local = row_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_equipment_periodically[1] is None \ + else row_equipment_periodically[1] + base[energy_category_id]['values_actual'].append(actual_value) + base[energy_category_id]['subtotal_actual'] += actual_value + base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate base period's energy savings + for i in range(len(base[energy_category_id]['values_plan'])): + base[energy_category_id]['values_saving'].append( + base[energy_category_id]['values_plan'][i] - + base[energy_category_id]['values_actual'][i]) + + base[energy_category_id]['subtotal_saving'] = \ + base[energy_category_id]['subtotal_plan'] - \ + base[energy_category_id]['subtotal_actual'] + base[energy_category_id]['subtotal_in_kgce_saving'] = \ + base[energy_category_id]['subtotal_in_kgce_plan'] - \ + base[energy_category_id]['subtotal_in_kgce_actual'] + base[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + base[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + base[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 5: query reporting period energy saving + ################################################################################################################ + reporting = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + reporting[energy_category_id] = dict() + reporting[energy_category_id]['timestamps'] = list() + reporting[energy_category_id]['values_plan'] = list() + reporting[energy_category_id]['values_actual'] = list() + reporting[energy_category_id]['values_saving'] = list() + reporting[energy_category_id]['subtotal_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query reporting period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (equipment['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_equipment_hourly = cursor_energy_plan.fetchall() + + rows_equipment_periodically = utilities.aggregate_hourly_data_by_period(rows_equipment_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_equipment_periodically in rows_equipment_periodically: + current_datetime_local = row_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_equipment_periodically[1] is None \ + else row_equipment_periodically[1] + reporting[energy_category_id]['timestamps'].append(current_datetime) + reporting[energy_category_id]['values_plan'].append(plan_value) + reporting[energy_category_id]['subtotal_plan'] += plan_value + reporting[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query reporting period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_equipment_input_category_hourly " + " WHERE equipment_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (equipment['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_equipment_hourly = cursor_energy.fetchall() + + rows_equipment_periodically = utilities.aggregate_hourly_data_by_period(rows_equipment_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_equipment_periodically in rows_equipment_periodically: + current_datetime_local = row_equipment_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_equipment_periodically[1] is None \ + else row_equipment_periodically[1] + reporting[energy_category_id]['values_actual'].append(actual_value) + reporting[energy_category_id]['subtotal_actual'] += actual_value + reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate reporting period's energy savings + for i in range(len(reporting[energy_category_id]['values_plan'])): + reporting[energy_category_id]['values_saving'].append( + reporting[energy_category_id]['values_plan'][i] - + reporting[energy_category_id]['values_actual'][i]) + + reporting[energy_category_id]['subtotal_saving'] = \ + reporting[energy_category_id]['subtotal_plan'] - \ + reporting[energy_category_id]['subtotal_actual'] + reporting[energy_category_id]['subtotal_in_kgce_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgce_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgce_actual'] + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 6: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if not is_quick_mode: + if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + energy_category_tariff_dict = utilities.get_energy_category_tariffs(equipment['cost_center_id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in energy_category_tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19][0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append(_('Tariff') + '-' + + energy_category_dict[energy_category_id]['name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 7: query associated points data + ################################################################################################################ + if not is_quick_mode: + for point in point_list: + point_values = [] + point_timestamps = [] + if point['object_type'] == 'ENERGY_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_energy_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'ANALOG_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_analog_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'DIGITAL_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_digital_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + + parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')') + parameters_data['timestamps'].append(point_timestamps) + parameters_data['values'].append(point_values) + + ################################################################################################################ + # Step 8: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + + result = dict() + + result['equipment'] = dict() + result['equipment']['name'] = equipment['name'] + + result['base_period'] = dict() + result['base_period']['names'] = list() + result['base_period']['units'] = list() + result['base_period']['timestamps'] = list() + result['base_period']['values_saving'] = list() + result['base_period']['subtotals_saving'] = list() + result['base_period']['subtotals_in_kgce_saving'] = list() + result['base_period']['subtotals_in_kgco2e_saving'] = list() + result['base_period']['total_in_kgce_saving'] = Decimal(0.0) + result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0) + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['base_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['base_period']['timestamps'].append(base[energy_category_id]['timestamps']) + result['base_period']['values_saving'].append(base[energy_category_id]['values_saving']) + result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving']) + result['base_period']['subtotals_in_kgce_saving'].append( + base[energy_category_id]['subtotal_in_kgce_saving']) + result['base_period']['subtotals_in_kgco2e_saving'].append( + base[energy_category_id]['subtotal_in_kgco2e_saving']) + result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving'] + result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving'] + + result['reporting_period'] = dict() + result['reporting_period']['names'] = list() + result['reporting_period']['energy_category_ids'] = list() + result['reporting_period']['units'] = list() + result['reporting_period']['timestamps'] = list() + result['reporting_period']['values_saving'] = list() + result['reporting_period']['rates_saving'] = list() + result['reporting_period']['subtotals_saving'] = list() + result['reporting_period']['subtotals_in_kgce_saving'] = list() + result['reporting_period']['subtotals_in_kgco2e_saving'] = list() + result['reporting_period']['increment_rates_saving'] = list() + result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0) + + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['reporting_period']['energy_category_ids'].append(energy_category_id) + result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps']) + result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving']) + result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving']) + result['reporting_period']['subtotals_in_kgce_saving'].append( + reporting[energy_category_id]['subtotal_in_kgce_saving']) + result['reporting_period']['subtotals_in_kgco2e_saving'].append( + reporting[energy_category_id]['subtotal_in_kgco2e_saving']) + result['reporting_period']['increment_rates_saving'].append( + (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) / + base[energy_category_id]['subtotal_saving'] + if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None) + result['reporting_period']['total_in_kgce_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgce_saving'] + result['reporting_period']['total_in_kgco2e_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] + + rate = list() + for index, value in enumerate(reporting[energy_category_id]['values_saving']): + if index < len(base[energy_category_id]['values_saving']) \ + and base[energy_category_id]['values_saving'][index] != 0 and value != 0: + rate.append((value - base[energy_category_id]['values_saving'][index]) + / base[energy_category_id]['values_saving'][index]) + else: + rate.append(None) + result['reporting_period']['rates_saving'].append(rate) + + result['reporting_period']['increment_rate_in_kgce_saving'] = \ + (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \ + result['base_period']['total_in_kgce_saving'] \ + if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None + + result['reporting_period']['increment_rate_in_kgco2e_saving'] = \ + (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \ + result['base_period']['total_in_kgco2e_saving'] \ + if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None + + result['parameters'] = { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + } + + # export result to Excel file and then encode the file to base64 string + result['excel_bytes_base64'] = None + if not is_quick_mode: + result['excel_bytes_base64'] = excelexporters.equipmentsaving.export(result, + equipment['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result) diff --git a/myems-api/reports/meterplan.py b/myems-api/reports/meterplan.py new file mode 100644 index 0000000000000000000000000000000000000000..16314f114cae7365cb5b8f664410610f0d8f6b9e --- /dev/null +++ b/myems-api/reports/meterplan.py @@ -0,0 +1,605 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json +import config +import excelexporters.metersaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """"Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the meter and energy category + # Step 3: query associated points + # Step 4: query base period energy saving + # Step 5: query reporting period energy saving + # Step 6: query tariff data + # Step 7: query associated points data + # Step 8: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + # this procedure accepts meter id or meter uuid to identify a meter + meter_id = req.params.get('meterid') + meter_uuid = req.params.get('meteruuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if meter_id is None and meter_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_METER_ID') + + if meter_id is not None: + meter_id = str.strip(meter_id) + if not meter_id.isdigit() or int(meter_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_METER_ID') + + if meter_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(meter_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_METER_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the meter and energy category + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() + + if meter_id is not None: + cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " + " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " + " FROM tbl_meters m, tbl_energy_categories ec " + " WHERE m.id = %s AND m.energy_category_id = ec.id ", (meter_id,)) + row_meter = cursor_system.fetchone() + elif meter_uuid is not None: + cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " + " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " + " FROM tbl_meters m, tbl_energy_categories ec " + " WHERE m.uuid = %s AND m.energy_category_id = ec.id ", (meter_uuid,)) + row_meter = cursor_system.fetchone() + + if row_meter is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.METER_NOT_FOUND') + + meter = dict() + meter['id'] = row_meter[0] + meter['name'] = row_meter[1] + meter['cost_center_id'] = row_meter[2] + meter['energy_category_id'] = row_meter[3] + meter['energy_category_name'] = row_meter[4] + meter['unit_of_measure'] = row_meter[5] + meter['kgce'] = row_meter[6] + meter['kgco2e'] = row_meter[7] + + ################################################################################################################ + # Step 3: query associated points + ################################################################################################################ + point_list = list() + cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type " + " FROM tbl_meters m, tbl_meters_points mp, tbl_points p " + " WHERE m.id = %s AND m.id = mp.meter_id AND mp.point_id = p.id " + " ORDER BY p.id ", (meter['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 4: query base period energy saving + ################################################################################################################ + base = dict() + base['timestamps'] = list() + base['values_plan'] = list() + base['values_actual'] = list() + base['values_saving'] = list() + base['total_in_category_plan'] = Decimal(0.0) + base['total_in_category_actual'] = Decimal(0.0) + base['total_in_category_saving'] = Decimal(0.0) + base['total_in_kgce_plan'] = Decimal(0.0) + base['total_in_kgce_actual'] = Decimal(0.0) + base['total_in_kgce_saving'] = Decimal(0.0) + base['total_in_kgco2e_plan'] = Decimal(0.0) + base['total_in_kgco2e_actual'] = Decimal(0.0) + base['total_in_kgco2e_saving'] = Decimal(0.0) + + # query base period plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_meter_hourly " + " WHERE meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (meter['id'], + base_start_datetime_utc, + base_end_datetime_utc)) + rows_meter_hourly = cursor_energy_plan.fetchall() + + rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + + for row_meter_periodically in rows_meter_periodically: + current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1] + base['timestamps'].append(current_datetime) + base['values_plan'].append(actual_value) + base['total_in_category_plan'] += actual_value + base['total_in_kgce_plan'] += actual_value * meter['kgce'] + base['total_in_kgco2e_plan'] += actual_value * meter['kgco2e'] + + # query base period actual + query = (" SELECT start_datetime_utc, actual_value " + " FROM tbl_meter_hourly " + " WHERE meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ") + cursor_energy.execute(query, (meter['id'], base_start_datetime_utc, base_end_datetime_utc)) + rows_meter_hourly = cursor_energy.fetchall() + + rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + + for row_meter_periodically in rows_meter_periodically: + current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1] + base['values_actual'].append(actual_value) + base['total_in_category_actual'] += actual_value + base['total_in_kgce_actual'] += actual_value * meter['kgce'] + base['total_in_kgco2e_actual'] += actual_value * meter['kgco2e'] + + # calculate base period saving + for i in range(len(base['values_plan'])): + base['values_saving'].append(base['values_plan'][i] - base['values_actual'][i]) + + base['total_in_category_saving'] = base['total_in_category_plan'] - base['total_in_category_actual'] + base['total_in_kgce_saving'] = base['total_in_kgce_plan'] - base['total_in_kgce_actual'] + base['total_in_kgco2e_saving'] = base['total_in_kgco2e_plan'] - base['total_in_kgco2e_actual'] + + ################################################################################################################ + # Step 5: query reporting period energy saving + ################################################################################################################ + reporting = dict() + reporting['timestamps'] = list() + reporting['values_plan'] = list() + reporting['values_actual'] = list() + reporting['values_saving'] = list() + reporting['rates_saving'] = list() + reporting['total_in_category_plan'] = Decimal(0.0) + reporting['total_in_category_actual'] = Decimal(0.0) + reporting['total_in_category_saving'] = Decimal(0.0) + reporting['total_in_kgce_plan'] = Decimal(0.0) + reporting['total_in_kgce_actual'] = Decimal(0.0) + reporting['total_in_kgce_saving'] = Decimal(0.0) + reporting['total_in_kgco2e_plan'] = Decimal(0.0) + reporting['total_in_kgco2e_actual'] = Decimal(0.0) + reporting['total_in_kgco2e_saving'] = Decimal(0.0) + + # query reporting period plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_meter_hourly " + " WHERE meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (meter['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_meter_hourly = cursor_energy_plan.fetchall() + + rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_meter_periodically in rows_meter_periodically: + current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1] + reporting['timestamps'].append(current_datetime) + reporting['values_plan'].append(actual_value) + reporting['total_in_category_plan'] += actual_value + reporting['total_in_kgce_plan'] += actual_value * meter['kgce'] + reporting['total_in_kgco2e_plan'] += actual_value * meter['kgco2e'] + + # query reporting period actual + query = (" SELECT start_datetime_utc, actual_value " + " FROM tbl_meter_hourly " + " WHERE meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ") + cursor_energy.execute(query, (meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) + rows_meter_hourly = cursor_energy.fetchall() + + rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_meter_periodically in rows_meter_periodically: + current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1] + reporting['values_actual'].append(actual_value) + reporting['total_in_category_actual'] += actual_value + reporting['total_in_kgce_actual'] += actual_value * meter['kgce'] + reporting['total_in_kgco2e_actual'] += actual_value * meter['kgco2e'] + + # calculate base period saving + for i in range(len(reporting['values_plan'])): + reporting['values_saving'].append(reporting['values_plan'][i] - reporting['values_actual'][i]) + + reporting['total_in_category_saving'] = \ + reporting['total_in_category_plan'] - reporting['total_in_category_actual'] + reporting['total_in_kgce_saving'] = \ + reporting['total_in_kgce_plan'] - reporting['total_in_kgce_actual'] + reporting['total_in_kgco2e_saving'] = \ + reporting['total_in_kgco2e_plan'] - reporting['total_in_kgco2e_actual'] + + for index, value in enumerate(reporting['values_saving']): + if index < len(base['values_saving']) and base['values_saving'][index] != 0 and value != 0: + reporting['rates_saving'].append((value - base['values_saving'][index]) / base['values_saving'][index]) + else: + reporting['rates_saving'].append(None) + + ################################################################################################################ + # Step 6: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if config.is_tariff_appended and not is_quick_mode: + tariff_dict = utilities.get_energy_category_tariffs(meter['cost_center_id'], + meter['energy_category_id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append(_('Tariff') + '-' + meter['energy_category_name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 7: query associated points data + ################################################################################################################ + if not is_quick_mode: + for point in point_list: + point_values = [] + point_timestamps = [] + if point['object_type'] == 'ENERGY_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_energy_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'ANALOG_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_analog_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'DIGITAL_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_digital_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + + parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')') + parameters_data['timestamps'].append(point_timestamps) + parameters_data['values'].append(point_values) + + ################################################################################################################ + # Step 8: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + result = { + "meter": { + "cost_center_id": meter['cost_center_id'], + "energy_category_id": meter['energy_category_id'], + "energy_category_name": meter['energy_category_name'], + "unit_of_measure": meter['unit_of_measure'], + "kgce": meter['kgce'], + "kgco2e": meter['kgco2e'], + }, + "base_period": { + "total_in_category_saving": base['total_in_category_saving'], + "total_in_kgce_saving": base['total_in_kgce_saving'], + "total_in_kgco2e_saving": base['total_in_kgco2e_saving'], + "timestamps": base['timestamps'], + "values_saving": base['values_saving'] + }, + "reporting_period": { + "increment_rate_saving": + (reporting['total_in_category_saving'] - base['total_in_category_saving']) / + base['total_in_category_saving'] + if base['total_in_category_saving'] != Decimal(0.0) else None, + "total_in_category_saving": reporting['total_in_category_saving'], + "total_in_kgce_saving": reporting['total_in_kgce_saving'], + "total_in_kgco2e_saving": reporting['total_in_kgco2e_saving'], + "timestamps": reporting['timestamps'], + "values_saving": reporting['values_saving'], + "rates_saving": reporting['rates_saving'], + }, + "parameters": { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + }, + } + # export result to Excel file and then encode the file to base64 string + if not is_quick_mode: + result['excel_bytes_base64'] = \ + excelexporters.metersaving.export(result, + meter['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result) diff --git a/myems-api/reports/offlinemeterplan.py b/myems-api/reports/offlinemeterplan.py new file mode 100644 index 0000000000000000000000000000000000000000..77ac368bd3f31dbfd7bf778888ac7ec918e2f649 --- /dev/null +++ b/myems-api/reports/offlinemeterplan.py @@ -0,0 +1,520 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json +import config +import excelexporters.offlinemetersaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """"Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the offline meter and energy category + # Step 3: query base period energy saving + # Step 4: query reporting period energy saving + # Step 5: query tariff data + # Step 6: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + offline_meter_id = req.params.get('offlinemeterid') + offline_meter_uuid = req.params.get('offlinemeteruuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if offline_meter_id is None and offline_meter_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_offline_meter_id') + + if offline_meter_id is not None: + offline_meter_id = str.strip(offline_meter_id) + if not offline_meter_id.isdigit() or int(offline_meter_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_offline_meter_id') + + if offline_meter_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(offline_meter_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_OFFLINE_METER_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the offline meter and energy category + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " + " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " + " FROM tbl_offline_meters m, tbl_energy_categories ec " + " WHERE m.id = %s AND m.energy_category_id = ec.id ", (offline_meter_id,)) + row_offline_meter = cursor_system.fetchone() + if row_offline_meter is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', + description='API.OFFLINE_METER_NOT_FOUND') + + offline_meter = dict() + offline_meter['id'] = row_offline_meter[0] + offline_meter['name'] = row_offline_meter[1] + offline_meter['cost_center_id'] = row_offline_meter[2] + offline_meter['energy_category_id'] = row_offline_meter[3] + offline_meter['energy_category_name'] = row_offline_meter[4] + offline_meter['unit_of_measure'] = row_offline_meter[5] + offline_meter['kgce'] = row_offline_meter[6] + offline_meter['kgco2e'] = row_offline_meter[7] + + ################################################################################################################ + # Step 3: query base period energy saving + ################################################################################################################ + base = dict() + base['timestamps'] = list() + base['values_plan'] = list() + base['values_actual'] = list() + base['values_saving'] = list() + base['total_in_category_plan'] = Decimal(0.0) + base['total_in_category_actual'] = Decimal(0.0) + base['total_in_category_saving'] = Decimal(0.0) + base['total_in_kgce_plan'] = Decimal(0.0) + base['total_in_kgce_actual'] = Decimal(0.0) + base['total_in_kgce_saving'] = Decimal(0.0) + base['total_in_kgco2e_plan'] = Decimal(0.0) + base['total_in_kgco2e_actual'] = Decimal(0.0) + base['total_in_kgco2e_saving'] = Decimal(0.0) + + # query base period plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_offline_meter_hourly " + " WHERE offline_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (offline_meter['id'], + base_start_datetime_utc, + base_end_datetime_utc)) + rows_offline_meter_hourly = cursor_energy_plan.fetchall() + + rows_offline_meter_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + + for row_offline_meter_periodically in rows_offline_meter_periodically: + current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ + else row_offline_meter_periodically[1] + base['timestamps'].append(current_datetime) + base['values_plan'].append(actual_value) + base['total_in_category_plan'] += actual_value + base['total_in_kgce_plan'] += actual_value * offline_meter['kgce'] + base['total_in_kgco2e_plan'] += actual_value * offline_meter['kgco2e'] + + # query base period actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_offline_meter_hourly " + " WHERE offline_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (offline_meter['id'], + base_start_datetime_utc, + base_end_datetime_utc)) + rows_offline_meter_hourly = cursor_energy.fetchall() + + rows_offline_meter_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + + for row_offline_meter_periodically in rows_offline_meter_periodically: + current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ + else row_offline_meter_periodically[1] + base['values_actual'].append(actual_value) + base['total_in_category_actual'] += actual_value + base['total_in_kgce_actual'] += actual_value * offline_meter['kgce'] + base['total_in_kgco2e_actual'] += actual_value * offline_meter['kgco2e'] + + # calculate base period saving + for i in range(len(base['values_plan'])): + base['values_saving'].append(base['values_plan'][i] - base['values_actual'][i]) + + base['total_in_category_saving'] = base['total_in_category_plan'] - base['total_in_category_actual'] + base['total_in_kgce_saving'] = base['total_in_kgce_plan'] - base['total_in_kgce_actual'] + base['total_in_kgco2e_saving'] = base['total_in_kgco2e_plan'] - base['total_in_kgco2e_actual'] + + ################################################################################################################ + # Step 3: query reporting period energy saving + ################################################################################################################ + reporting = dict() + reporting['timestamps'] = list() + reporting['values_plan'] = list() + reporting['values_actual'] = list() + reporting['values_saving'] = list() + reporting['values_rates'] = list() + reporting['total_in_category_plan'] = Decimal(0.0) + reporting['total_in_category_actual'] = Decimal(0.0) + reporting['total_in_category_saving'] = Decimal(0.0) + reporting['total_in_kgce_plan'] = Decimal(0.0) + reporting['total_in_kgce_actual'] = Decimal(0.0) + reporting['total_in_kgce_saving'] = Decimal(0.0) + reporting['total_in_kgco2e_plan'] = Decimal(0.0) + reporting['total_in_kgco2e_actual'] = Decimal(0.0) + reporting['total_in_kgco2e_saving'] = Decimal(0.0) + # query reporting period plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_offline_meter_hourly " + " WHERE offline_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (offline_meter['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_offline_meter_hourly = cursor_energy_plan.fetchall() + + rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_offline_meter_periodically in rows_offline_meter_periodically: + current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ + else row_offline_meter_periodically[1] + + reporting['timestamps'].append(current_datetime) + reporting['values_plan'].append(actual_value) + reporting['total_in_category_plan'] += actual_value + reporting['total_in_kgce_plan'] += actual_value * offline_meter['kgce'] + reporting['total_in_kgco2e_plan'] += actual_value * offline_meter['kgco2e'] + + # query reporting period actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_offline_meter_hourly " + " WHERE offline_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (offline_meter['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_offline_meter_hourly = cursor_energy.fetchall() + + rows_offline_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_offline_meter_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_offline_meter_periodically in rows_offline_meter_periodically: + current_datetime_local = row_offline_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_offline_meter_periodically[1] is None \ + else row_offline_meter_periodically[1] + + reporting['values_actual'].append(actual_value) + reporting['total_in_category_actual'] += actual_value + reporting['total_in_kgce_actual'] += actual_value * offline_meter['kgce'] + reporting['total_in_kgco2e_actual'] += actual_value * offline_meter['kgco2e'] + + # calculate base period saving + for i in range(len(reporting['values_plan'])): + reporting['values_saving'].append(reporting['values_plan'][i] - reporting['values_actual'][i]) + + reporting['total_in_category_saving'] = \ + reporting['total_in_category_plan'] - reporting['total_in_category_actual'] + reporting['total_in_kgce_saving'] = \ + reporting['total_in_kgce_plan'] - reporting['total_in_kgce_actual'] + reporting['total_in_kgco2e_saving'] = \ + reporting['total_in_kgco2e_plan'] - reporting['total_in_kgco2e_actual'] + + for index, value in enumerate(reporting['values_saving']): + if index < len(base['values_saving']) and base['values_saving'][index] != 0 and value != 0: + reporting['values_rates'].append((value - base['values_saving'][index]) / base['values_saving'][index]) + else: + reporting['values_rates'].append(None) + + ################################################################################################################ + # Step 5: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if config.is_tariff_appended and not is_quick_mode: + tariff_dict = utilities.get_energy_category_tariffs(offline_meter['cost_center_id'], + offline_meter['energy_category_id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append(_('Tariff') + '-' + offline_meter['energy_category_name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 6: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + result = { + "offline_meter": { + "cost_center_id": offline_meter['cost_center_id'], + "energy_category_id": offline_meter['energy_category_id'], + "energy_category_name": offline_meter['energy_category_name'], + "unit_of_measure": offline_meter['unit_of_measure'], + "kgce": offline_meter['kgce'], + "kgco2e": offline_meter['kgco2e'], + }, + "base_period": { + "total_in_category_saving": base['total_in_category_saving'], + "total_in_kgce_saving": base['total_in_kgce_saving'], + "total_in_kgco2e_saving": base['total_in_kgco2e_saving'], + "timestamps": base['timestamps'], + "values_saving": base['values_saving'], + }, + "reporting_period": { + "increment_rate_saving": + (reporting['total_in_category_saving'] - base['total_in_category_saving']) / + base['total_in_category_saving'] + if base['total_in_category_saving'] != Decimal(0.0) else None, + "total_in_category_saving": reporting['total_in_category_saving'], + "total_in_kgce_saving": reporting['total_in_kgce_saving'], + "total_in_kgco2e_saving": reporting['total_in_kgco2e_saving'], + "timestamps": reporting['timestamps'], + "values_saving": reporting['values_saving'], + "values_rates": reporting['values_rates'], + }, + "parameters": { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + }, + } + + # export result to Excel file and then encode the file to base64 string + if not is_quick_mode: + result['excel_bytes_base64'] = \ + excelexporters.offlinemetersaving.export(result, + offline_meter['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result) diff --git a/myems-api/reports/shopfloorplan.py b/myems-api/reports/shopfloorplan.py new file mode 100644 index 0000000000000000000000000000000000000000..5eb3ee39ec2ee2b08d5a2a34444768229871ae55 --- /dev/null +++ b/myems-api/reports/shopfloorplan.py @@ -0,0 +1,780 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json +import config +import excelexporters.shopfloorsaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """"Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the shopfloor + # Step 3: query energy categories + # Step 4: query associated sensors + # Step 5: query associated points + # Step 6: query base period energy saving + # Step 7: query reporting period energy saving + # Step 8: query tariff data + # Step 9: query associated sensors and points data + # Step 10: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + shopfloor_id = req.params.get('shopfloorid') + shopfloor_uuid = req.params.get('shopflooruuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if shopfloor_id is None and shopfloor_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_SHOPFLOOR_ID') + + if shopfloor_id is not None: + shopfloor_id = str.strip(shopfloor_id) + if not shopfloor_id.isdigit() or int(shopfloor_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_SHOPFLOOR_ID') + + if shopfloor_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(shopfloor_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_SHOPFLOOR_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the shopfloor + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() + + if shopfloor_id is not None: + cursor_system.execute(" SELECT id, name, area, cost_center_id " + " FROM tbl_shopfloors " + " WHERE id = %s ", (shopfloor_id,)) + row_shopfloor = cursor_system.fetchone() + elif shopfloor_uuid is not None: + cursor_system.execute(" SELECT id, name, area, cost_center_id " + " FROM tbl_shopfloors " + " WHERE uuid = %s ", (shopfloor_uuid,)) + row_shopfloor = cursor_system.fetchone() + + if row_shopfloor is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.SHOPFLOOR_NOT_FOUND') + + shopfloor = dict() + shopfloor['id'] = row_shopfloor[0] + shopfloor['name'] = row_shopfloor[1] + shopfloor['area'] = row_shopfloor[2] + shopfloor['cost_center_id'] = row_shopfloor[3] + + ################################################################################################################ + # Step 3: query energy categories + ################################################################################################################ + energy_category_set = set() + # query energy categories in base period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_shopfloor_input_category_hourly " + " WHERE shopfloor_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (shopfloor['id'], base_start_datetime_utc, base_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query energy categories in reporting period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_shopfloor_input_category_hourly " + " WHERE shopfloor_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (shopfloor['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query all energy categories in base period and reporting period + cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e " + " FROM tbl_energy_categories " + " ORDER BY id ", ) + rows_energy_categories = cursor_system.fetchall() + if rows_energy_categories is None or len(rows_energy_categories) == 0: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, + title='API.NOT_FOUND', + description='API.ENERGY_CATEGORY_NOT_FOUND') + energy_category_dict = dict() + for row_energy_category in rows_energy_categories: + if row_energy_category[0] in energy_category_set: + energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1], + "unit_of_measure": row_energy_category[2], + "kgce": row_energy_category[3], + "kgco2e": row_energy_category[4]} + + ################################################################################################################ + # Step 4: query associated sensors + ################################################################################################################ + point_list = list() + cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type " + " FROM tbl_shopfloors st, tbl_sensors se, tbl_shopfloors_sensors ss, " + " tbl_points p, tbl_sensors_points sp " + " WHERE st.id = %s AND st.id = ss.shopfloor_id AND ss.sensor_id = se.id " + " AND se.id = sp.sensor_id AND sp.point_id = p.id " + " ORDER BY p.id ", (shopfloor['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 5: query associated points + ################################################################################################################ + cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type " + " FROM tbl_shopfloors s, tbl_shopfloors_points sp, tbl_points p " + " WHERE s.id = %s AND s.id = sp.shopfloor_id AND sp.point_id = p.id " + " ORDER BY p.id ", (shopfloor['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 6: query base period energy saving + ################################################################################################################ + base = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + base[energy_category_id] = dict() + base[energy_category_id]['timestamps'] = list() + base[energy_category_id]['values_plan'] = list() + base[energy_category_id]['values_actual'] = list() + base[energy_category_id]['values_saving'] = list() + base[energy_category_id]['subtotal_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query base period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_shopfloor_input_category_hourly " + " WHERE shopfloor_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (shopfloor['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_shopfloor_hourly = cursor_energy_plan.fetchall() + + rows_shopfloor_periodically = utilities.aggregate_hourly_data_by_period(rows_shopfloor_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_shopfloor_periodically in rows_shopfloor_periodically: + current_datetime_local = row_shopfloor_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_shopfloor_periodically[1] is None \ + else row_shopfloor_periodically[1] + base[energy_category_id]['timestamps'].append(current_datetime) + base[energy_category_id]['values_plan'].append(plan_value) + base[energy_category_id]['subtotal_plan'] += plan_value + base[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query base period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_shopfloor_input_category_hourly " + " WHERE shopfloor_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (shopfloor['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_shopfloor_hourly = cursor_energy.fetchall() + + rows_shopfloor_periodically = utilities.aggregate_hourly_data_by_period(rows_shopfloor_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_shopfloor_periodically in rows_shopfloor_periodically: + current_datetime_local = row_shopfloor_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_shopfloor_periodically[1] is None \ + else row_shopfloor_periodically[1] + base[energy_category_id]['values_actual'].append(actual_value) + base[energy_category_id]['subtotal_actual'] += actual_value + base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate base period's energy savings + for i in range(len(base[energy_category_id]['values_plan'])): + base[energy_category_id]['values_saving'].append( + base[energy_category_id]['values_plan'][i] - + base[energy_category_id]['values_actual'][i]) + + base[energy_category_id]['subtotal_saving'] = \ + base[energy_category_id]['subtotal_plan'] - \ + base[energy_category_id]['subtotal_actual'] + base[energy_category_id]['subtotal_in_kgce_saving'] = \ + base[energy_category_id]['subtotal_in_kgce_plan'] - \ + base[energy_category_id]['subtotal_in_kgce_actual'] + base[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + base[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + base[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 7: query reporting period energy saving + ################################################################################################################ + reporting = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + reporting[energy_category_id] = dict() + reporting[energy_category_id]['timestamps'] = list() + reporting[energy_category_id]['values_plan'] = list() + reporting[energy_category_id]['values_actual'] = list() + reporting[energy_category_id]['values_saving'] = list() + reporting[energy_category_id]['subtotal_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query reporting period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_shopfloor_input_category_hourly " + " WHERE shopfloor_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (shopfloor['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_shopfloor_hourly = cursor_energy_plan.fetchall() + + rows_shopfloor_periodically = utilities.aggregate_hourly_data_by_period(rows_shopfloor_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_shopfloor_periodically in rows_shopfloor_periodically: + current_datetime_local = row_shopfloor_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_shopfloor_periodically[1] is None \ + else row_shopfloor_periodically[1] + reporting[energy_category_id]['timestamps'].append(current_datetime) + reporting[energy_category_id]['values_plan'].append(plan_value) + reporting[energy_category_id]['subtotal_plan'] += plan_value + reporting[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query reporting period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_shopfloor_input_category_hourly " + " WHERE shopfloor_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (shopfloor['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_shopfloor_hourly = cursor_energy.fetchall() + + rows_shopfloor_periodically = utilities.aggregate_hourly_data_by_period(rows_shopfloor_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_shopfloor_periodically in rows_shopfloor_periodically: + current_datetime_local = row_shopfloor_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_shopfloor_periodically[1] is None \ + else row_shopfloor_periodically[1] + reporting[energy_category_id]['values_actual'].append(actual_value) + reporting[energy_category_id]['subtotal_actual'] += actual_value + reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate reporting period's energy savings + for i in range(len(reporting[energy_category_id]['values_plan'])): + reporting[energy_category_id]['values_saving'].append( + reporting[energy_category_id]['values_plan'][i] - + reporting[energy_category_id]['values_actual'][i]) + + reporting[energy_category_id]['subtotal_saving'] = \ + reporting[energy_category_id]['subtotal_plan'] - \ + reporting[energy_category_id]['subtotal_actual'] + reporting[energy_category_id]['subtotal_in_kgce_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgce_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgce_actual'] + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 8: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0 \ + and not is_quick_mode: + for energy_category_id in energy_category_set: + energy_category_tariff_dict = utilities.get_energy_category_tariffs(shopfloor['cost_center_id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in energy_category_tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19][0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append(_('Tariff') + '-' + energy_category_dict[energy_category_id]['name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 9: query associated sensors and points data + ################################################################################################################ + if not is_quick_mode: + for point in point_list: + point_values = [] + point_timestamps = [] + if point['object_type'] == 'ENERGY_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_energy_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'ANALOG_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_analog_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'DIGITAL_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_digital_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + + parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')') + parameters_data['timestamps'].append(point_timestamps) + parameters_data['values'].append(point_values) + + ################################################################################################################ + # Step 10: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + + result = dict() + + result['shopfloor'] = dict() + result['shopfloor']['name'] = shopfloor['name'] + result['shopfloor']['area'] = shopfloor['area'] + + result['base_period'] = dict() + result['base_period']['names'] = list() + result['base_period']['units'] = list() + result['base_period']['timestamps'] = list() + result['base_period']['values_saving'] = list() + result['base_period']['subtotals_saving'] = list() + result['base_period']['subtotals_in_kgce_saving'] = list() + result['base_period']['subtotals_in_kgco2e_saving'] = list() + result['base_period']['total_in_kgce_saving'] = Decimal(0.0) + result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0) + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['base_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['base_period']['timestamps'].append(base[energy_category_id]['timestamps']) + result['base_period']['values_saving'].append(base[energy_category_id]['values_saving']) + result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving']) + result['base_period']['subtotals_in_kgce_saving'].append( + base[energy_category_id]['subtotal_in_kgce_saving']) + result['base_period']['subtotals_in_kgco2e_saving'].append( + base[energy_category_id]['subtotal_in_kgco2e_saving']) + result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving'] + result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving'] + + result['reporting_period'] = dict() + result['reporting_period']['names'] = list() + result['reporting_period']['energy_category_ids'] = list() + result['reporting_period']['units'] = list() + result['reporting_period']['timestamps'] = list() + result['reporting_period']['values_saving'] = list() + result['reporting_period']['rates_saving'] = list() + result['reporting_period']['subtotals_saving'] = list() + result['reporting_period']['subtotals_in_kgce_saving'] = list() + result['reporting_period']['subtotals_in_kgco2e_saving'] = list() + result['reporting_period']['subtotals_per_unit_area_saving'] = list() + result['reporting_period']['increment_rates_saving'] = list() + result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0) + + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['reporting_period']['energy_category_ids'].append(energy_category_id) + result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps']) + result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving']) + result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving']) + result['reporting_period']['subtotals_in_kgce_saving'].append( + reporting[energy_category_id]['subtotal_in_kgce_saving']) + result['reporting_period']['subtotals_in_kgco2e_saving'].append( + reporting[energy_category_id]['subtotal_in_kgco2e_saving']) + result['reporting_period']['subtotals_per_unit_area_saving'].append( + reporting[energy_category_id]['subtotal_saving'] / shopfloor['area'] + if shopfloor['area'] > Decimal(0.0) else None) + result['reporting_period']['increment_rates_saving'].append( + (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) / + base[energy_category_id]['subtotal_saving'] + if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None) + result['reporting_period']['total_in_kgce_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgce_saving'] + result['reporting_period']['total_in_kgco2e_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] + + rate = list() + for index, value in enumerate(reporting[energy_category_id]['values_saving']): + if index < len(base[energy_category_id]['values_saving']) \ + and base[energy_category_id]['values_saving'][index] != 0 and value != 0: + rate.append((value - base[energy_category_id]['values_saving'][index]) + / base[energy_category_id]['values_saving'][index]) + else: + rate.append(None) + result['reporting_period']['rates_saving'].append(rate) + + result['reporting_period']['total_in_kgco2e_per_unit_area_saving'] = \ + result['reporting_period']['total_in_kgce_saving'] / shopfloor['area'] \ + if shopfloor['area'] > 0.0 else None + + result['reporting_period']['increment_rate_in_kgce_saving'] = \ + (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \ + result['base_period']['total_in_kgce_saving'] \ + if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None + + result['reporting_period']['total_in_kgce_per_unit_area_saving'] = \ + result['reporting_period']['total_in_kgco2e_saving'] / shopfloor['area'] \ + if shopfloor['area'] > Decimal(0.0) else None + + result['reporting_period']['increment_rate_in_kgco2e_saving'] = \ + (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \ + result['base_period']['total_in_kgco2e_saving'] \ + if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None + + result['parameters'] = { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + } + + result['excel_bytes_base64'] = None + if not is_quick_mode: + result['excel_bytes_base64'] = excelexporters.shopfloorsaving.export(result, + shopfloor['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result) diff --git a/myems-api/reports/storeplan.py b/myems-api/reports/storeplan.py new file mode 100644 index 0000000000000000000000000000000000000000..30bb1b6ad049cec39c69d44c9f2dee7404acecef --- /dev/null +++ b/myems-api/reports/storeplan.py @@ -0,0 +1,776 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json +import config +import excelexporters.storesaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """ Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the store + # Step 3: query energy categories + # Step 4: query associated sensors + # Step 5: query associated points + # Step 6: query base period energy saving + # Step 7: query reporting period energy saving + # Step 8: query tariff data + # Step 9: query associated sensors and points data + # Step 10: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + store_id = req.params.get('storeid') + store_uuid = req.params.get('storeuuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if store_id is None and store_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_STORE_ID') + + if store_id is not None: + store_id = str.strip(store_id) + if not store_id.isdigit() or int(store_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_STORE_ID') + + if store_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(store_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_STORE_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the store + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() + + if store_id is not None: + cursor_system.execute(" SELECT id, name, area, cost_center_id " + " FROM tbl_stores " + " WHERE id = %s ", (store_id,)) + row_store = cursor_system.fetchone() + elif store_uuid is not None: + cursor_system.execute(" SELECT id, name, area, cost_center_id " + " FROM tbl_stores " + " WHERE uuid = %s ", (store_uuid,)) + row_store = cursor_system.fetchone() + + if row_store is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.STORE_NOT_FOUND') + + store = dict() + store['id'] = row_store[0] + store['name'] = row_store[1] + store['area'] = row_store[2] + store['cost_center_id'] = row_store[3] + + ################################################################################################################ + # Step 3: query energy categories + ################################################################################################################ + energy_category_set = set() + # query energy categories in base period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_store_input_category_hourly " + " WHERE store_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (store['id'], base_start_datetime_utc, base_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query energy categories in reporting period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_store_input_category_hourly " + " WHERE store_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (store['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query all energy categories in base period and reporting period + cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e " + " FROM tbl_energy_categories " + " ORDER BY id ", ) + rows_energy_categories = cursor_system.fetchall() + if rows_energy_categories is None or len(rows_energy_categories) == 0: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, + title='API.NOT_FOUND', + description='API.ENERGY_CATEGORY_NOT_FOUND') + energy_category_dict = dict() + for row_energy_category in rows_energy_categories: + if row_energy_category[0] in energy_category_set: + energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1], + "unit_of_measure": row_energy_category[2], + "kgce": row_energy_category[3], + "kgco2e": row_energy_category[4]} + + ################################################################################################################ + # Step 4: query associated sensors + ################################################################################################################ + point_list = list() + cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type " + " FROM tbl_stores st, tbl_sensors se, tbl_stores_sensors ss, " + " tbl_points p, tbl_sensors_points sp " + " WHERE st.id = %s AND st.id = ss.store_id AND ss.sensor_id = se.id " + " AND se.id = sp.sensor_id AND sp.point_id = p.id " + " ORDER BY p.id ", (store['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 5: query associated points + ################################################################################################################ + cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type " + " FROM tbl_stores s, tbl_stores_points sp, tbl_points p " + " WHERE s.id = %s AND s.id = sp.store_id AND sp.point_id = p.id " + " ORDER BY p.id ", (store['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 6: query base period energy saving + ################################################################################################################ + base = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + base[energy_category_id] = dict() + base[energy_category_id]['timestamps'] = list() + base[energy_category_id]['values_plan'] = list() + base[energy_category_id]['values_actual'] = list() + base[energy_category_id]['values_saving'] = list() + base[energy_category_id]['subtotal_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query base period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_store_input_category_hourly " + " WHERE store_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (store['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_store_hourly = cursor_energy_plan.fetchall() + + rows_store_periodically = utilities.aggregate_hourly_data_by_period(rows_store_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_store_periodically in rows_store_periodically: + current_datetime_local = row_store_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_store_periodically[1] is None else row_store_periodically[1] + base[energy_category_id]['timestamps'].append(current_datetime) + base[energy_category_id]['values_plan'].append(plan_value) + base[energy_category_id]['subtotal_plan'] += plan_value + base[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query base period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_store_input_category_hourly " + " WHERE store_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (store['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_store_hourly = cursor_energy.fetchall() + + rows_store_periodically = utilities.aggregate_hourly_data_by_period(rows_store_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_store_periodically in rows_store_periodically: + current_datetime_local = row_store_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_store_periodically[1] is None else row_store_periodically[1] + base[energy_category_id]['values_actual'].append(actual_value) + base[energy_category_id]['subtotal_actual'] += actual_value + base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate base period's energy savings + for i in range(len(base[energy_category_id]['values_plan'])): + base[energy_category_id]['values_saving'].append( + base[energy_category_id]['values_plan'][i] - + base[energy_category_id]['values_actual'][i]) + + base[energy_category_id]['subtotal_saving'] = \ + base[energy_category_id]['subtotal_plan'] - \ + base[energy_category_id]['subtotal_actual'] + base[energy_category_id]['subtotal_in_kgce_saving'] = \ + base[energy_category_id]['subtotal_in_kgce_plan'] - \ + base[energy_category_id]['subtotal_in_kgce_actual'] + base[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + base[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + base[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 7: query reporting period energy saving + ################################################################################################################ + reporting = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + reporting[energy_category_id] = dict() + reporting[energy_category_id]['timestamps'] = list() + reporting[energy_category_id]['values_plan'] = list() + reporting[energy_category_id]['values_actual'] = list() + reporting[energy_category_id]['values_saving'] = list() + reporting[energy_category_id]['subtotal_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query reporting period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_store_input_category_hourly " + " WHERE store_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (store['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_store_hourly = cursor_energy_plan.fetchall() + + rows_store_periodically = utilities.aggregate_hourly_data_by_period(rows_store_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_store_periodically in rows_store_periodically: + current_datetime_local = row_store_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_store_periodically[1] is None else row_store_periodically[1] + reporting[energy_category_id]['timestamps'].append(current_datetime) + reporting[energy_category_id]['values_plan'].append(plan_value) + reporting[energy_category_id]['subtotal_plan'] += plan_value + reporting[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query reporting period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_store_input_category_hourly " + " WHERE store_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (store['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_store_hourly = cursor_energy.fetchall() + + rows_store_periodically = utilities.aggregate_hourly_data_by_period(rows_store_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_store_periodically in rows_store_periodically: + current_datetime_local = row_store_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_store_periodically[1] is None else row_store_periodically[1] + reporting[energy_category_id]['values_actual'].append(actual_value) + reporting[energy_category_id]['subtotal_actual'] += actual_value + reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate reporting period's energy savings + for i in range(len(reporting[energy_category_id]['values_plan'])): + reporting[energy_category_id]['values_saving'].append( + reporting[energy_category_id]['values_plan'][i] - + reporting[energy_category_id]['values_actual'][i]) + + reporting[energy_category_id]['subtotal_saving'] = \ + reporting[energy_category_id]['subtotal_plan'] - \ + reporting[energy_category_id]['subtotal_actual'] + reporting[energy_category_id]['subtotal_in_kgce_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgce_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgce_actual'] + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 8: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0 \ + and not is_quick_mode: + for energy_category_id in energy_category_set: + energy_category_tariff_dict = utilities.get_energy_category_tariffs(store['cost_center_id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in energy_category_tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19][0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append(_('Tariff') + '-' + energy_category_dict[energy_category_id]['name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 9: query associated sensors and points data + ################################################################################################################ + if not is_quick_mode: + for point in point_list: + point_values = [] + point_timestamps = [] + if point['object_type'] == 'ENERGY_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_energy_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'ANALOG_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_analog_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'DIGITAL_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_digital_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + + parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')') + parameters_data['timestamps'].append(point_timestamps) + parameters_data['values'].append(point_values) + + ################################################################################################################ + # Step 10: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + + result = dict() + + result['store'] = dict() + result['store']['name'] = store['name'] + result['store']['area'] = store['area'] + + result['base_period'] = dict() + result['base_period']['names'] = list() + result['base_period']['units'] = list() + result['base_period']['timestamps'] = list() + result['base_period']['values_saving'] = list() + result['base_period']['subtotals_saving'] = list() + result['base_period']['subtotals_in_kgce_saving'] = list() + result['base_period']['subtotals_in_kgco2e_saving'] = list() + result['base_period']['total_in_kgce_saving'] = Decimal(0.0) + result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0) + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['base_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['base_period']['timestamps'].append(base[energy_category_id]['timestamps']) + result['base_period']['values_saving'].append(base[energy_category_id]['values_saving']) + result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving']) + result['base_period']['subtotals_in_kgce_saving'].append( + base[energy_category_id]['subtotal_in_kgce_saving']) + result['base_period']['subtotals_in_kgco2e_saving'].append( + base[energy_category_id]['subtotal_in_kgco2e_saving']) + result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving'] + result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving'] + + result['reporting_period'] = dict() + result['reporting_period']['names'] = list() + result['reporting_period']['energy_category_ids'] = list() + result['reporting_period']['units'] = list() + result['reporting_period']['timestamps'] = list() + result['reporting_period']['values_saving'] = list() + result['reporting_period']['rates_saving'] = list() + result['reporting_period']['subtotals_saving'] = list() + result['reporting_period']['subtotals_in_kgce_saving'] = list() + result['reporting_period']['subtotals_in_kgco2e_saving'] = list() + result['reporting_period']['subtotals_per_unit_area_saving'] = list() + result['reporting_period']['increment_rates_saving'] = list() + result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0) + + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['reporting_period']['energy_category_ids'].append(energy_category_id) + result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps']) + result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving']) + result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving']) + result['reporting_period']['subtotals_in_kgce_saving'].append( + reporting[energy_category_id]['subtotal_in_kgce_saving']) + result['reporting_period']['subtotals_in_kgco2e_saving'].append( + reporting[energy_category_id]['subtotal_in_kgco2e_saving']) + result['reporting_period']['subtotals_per_unit_area_saving'].append( + reporting[energy_category_id]['subtotal_saving'] / store['area'] + if store['area'] > Decimal(0.0) else None) + result['reporting_period']['increment_rates_saving'].append( + (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) / + base[energy_category_id]['subtotal_saving'] + if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None) + result['reporting_period']['total_in_kgce_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgce_saving'] + result['reporting_period']['total_in_kgco2e_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] + + rate = list() + for index, value in enumerate(reporting[energy_category_id]['values_saving']): + if index < len(base[energy_category_id]['values_saving']) \ + and base[energy_category_id]['values_saving'][index] != 0 and value != 0: + rate.append((value - base[energy_category_id]['values_saving'][index]) + / base[energy_category_id]['values_saving'][index]) + else: + rate.append(None) + result['reporting_period']['rates_saving'].append(rate) + + result['reporting_period']['total_in_kgco2e_per_unit_area_saving'] = \ + result['reporting_period']['total_in_kgce_saving'] / store['area'] \ + if store['area'] > Decimal(0.0) else None + + result['reporting_period']['increment_rate_in_kgce_saving'] = \ + (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \ + result['base_period']['total_in_kgce_saving'] \ + if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None + + result['reporting_period']['total_in_kgce_per_unit_area_saving'] = \ + result['reporting_period']['total_in_kgco2e_saving'] / store['area'] \ + if store['area'] > Decimal(0.0) else None + + result['reporting_period']['increment_rate_in_kgco2e_saving'] = \ + (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \ + result['base_period']['total_in_kgco2e_saving'] \ + if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None + + result['parameters'] = { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + } + + # export result to Excel file and then encode the file to base64 string + if not is_quick_mode: + result['excel_bytes_base64'] = excelexporters.storesaving.export(result, + store['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result) diff --git a/myems-api/reports/tenantplan.py b/myems-api/reports/tenantplan.py new file mode 100644 index 0000000000000000000000000000000000000000..ede85714643f3b32f39888538320f1378c56f95c --- /dev/null +++ b/myems-api/reports/tenantplan.py @@ -0,0 +1,777 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json + +import config +import excelexporters.tenantsaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """"Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the tenant + # Step 3: query energy categories + # Step 4: query associated sensors + # Step 5: query associated points + # Step 6: query base period energy saving + # Step 7: query reporting period energy saving + # Step 8: query tariff data + # Step 9: query associated sensors and points data + # Step 10: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + tenant_id = req.params.get('tenantid') + tenant_uuid = req.params.get('tenantuuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if tenant_id is None and tenant_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_TENANT_ID') + + if tenant_id is not None: + tenant_id = str.strip(tenant_id) + if not tenant_id.isdigit() or int(tenant_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_TENANT_ID') + + if tenant_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(tenant_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_TENANT_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the tenant + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cnx_historical = mysql.connector.connect(**config.myems_historical_db) + cursor_historical = cnx_historical.cursor() + + if tenant_id is not None: + cursor_system.execute(" SELECT id, name, area, cost_center_id " + " FROM tbl_tenants " + " WHERE id = %s ", (tenant_id,)) + row_tenant = cursor_system.fetchone() + elif tenant_uuid is not None: + cursor_system.execute(" SELECT id, name, area, cost_center_id " + " FROM tbl_tenants " + " WHERE uuid = %s ", (tenant_uuid,)) + row_tenant = cursor_system.fetchone() + + if row_tenant is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.TENANT_NOT_FOUND') + + tenant = dict() + tenant['id'] = row_tenant[0] + tenant['name'] = row_tenant[1] + tenant['area'] = row_tenant[2] + tenant['cost_center_id'] = row_tenant[3] + + ################################################################################################################ + # Step 3: query energy categories + ################################################################################################################ + energy_category_set = set() + # query energy categories in base period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_tenant_input_category_hourly " + " WHERE tenant_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (tenant['id'], base_start_datetime_utc, base_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query energy categories in reporting period + cursor_energy.execute(" SELECT DISTINCT(energy_category_id) " + " FROM tbl_tenant_input_category_hourly " + " WHERE tenant_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s ", + (tenant['id'], reporting_start_datetime_utc, reporting_end_datetime_utc)) + rows_energy_categories = cursor_energy.fetchall() + if rows_energy_categories is not None and len(rows_energy_categories) > 0: + for row_energy_category in rows_energy_categories: + energy_category_set.add(row_energy_category[0]) + + # query all energy categories in base period and reporting period + cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e " + " FROM tbl_energy_categories " + " ORDER BY id ", ) + rows_energy_categories = cursor_system.fetchall() + if rows_energy_categories is None or len(rows_energy_categories) == 0: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + raise falcon.HTTPError(status=falcon.HTTP_404, + title='API.NOT_FOUND', + description='API.ENERGY_CATEGORY_NOT_FOUND') + energy_category_dict = dict() + for row_energy_category in rows_energy_categories: + if row_energy_category[0] in energy_category_set: + energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1], + "unit_of_measure": row_energy_category[2], + "kgce": row_energy_category[3], + "kgco2e": row_energy_category[4]} + + ################################################################################################################ + # Step 4: query associated sensors + ################################################################################################################ + point_list = list() + cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type " + " FROM tbl_tenants t, tbl_sensors s, tbl_tenants_sensors ts, " + " tbl_points p, tbl_sensors_points sp " + " WHERE t.id = %s AND t.id = ts.tenant_id AND ts.sensor_id = s.id " + " AND s.id = sp.sensor_id AND sp.point_id = p.id " + " ORDER BY p.id ", (tenant['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 5: query associated points + ################################################################################################################ + cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type " + " FROM tbl_tenants t, tbl_tenants_points tp, tbl_points p " + " WHERE t.id = %s AND t.id = tp.tenant_id AND tp.point_id = p.id " + " ORDER BY p.id ", (tenant['id'],)) + rows_points = cursor_system.fetchall() + if rows_points is not None and len(rows_points) > 0: + for row in rows_points: + point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]}) + + ################################################################################################################ + # Step 6: query base period energy saving + ################################################################################################################ + base = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + base[energy_category_id] = dict() + base[energy_category_id]['timestamps'] = list() + base[energy_category_id]['values_plan'] = list() + base[energy_category_id]['values_actual'] = list() + base[energy_category_id]['values_saving'] = list() + base[energy_category_id]['subtotal_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query base period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_tenant_input_category_hourly " + " WHERE tenant_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (tenant['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_tenant_hourly = cursor_energy_plan.fetchall() + + rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_tenant_periodically in rows_tenant_periodically: + current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1] + base[energy_category_id]['timestamps'].append(current_datetime) + base[energy_category_id]['values_plan'].append(plan_value) + base[energy_category_id]['subtotal_plan'] += plan_value + base[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query base period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_tenant_input_category_hourly " + " WHERE tenant_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (tenant['id'], + energy_category_id, + base_start_datetime_utc, + base_end_datetime_utc)) + rows_tenant_hourly = cursor_energy.fetchall() + + rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + for row_tenant_periodically in rows_tenant_periodically: + current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1] + base[energy_category_id]['values_actual'].append(actual_value) + base[energy_category_id]['subtotal_actual'] += actual_value + base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate base period's energy saving + for i in range(len(base[energy_category_id]['values_plan'])): + base[energy_category_id]['values_saving'].append( + base[energy_category_id]['values_plan'][i] - + base[energy_category_id]['values_actual'][i]) + + base[energy_category_id]['subtotal_saving'] = \ + base[energy_category_id]['subtotal_plan'] - \ + base[energy_category_id]['subtotal_actual'] + base[energy_category_id]['subtotal_in_kgce_saving'] = \ + base[energy_category_id]['subtotal_in_kgce_plan'] - \ + base[energy_category_id]['subtotal_in_kgce_actual'] + base[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + base[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + base[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 7: query reporting period energy saving + ################################################################################################################ + reporting = dict() + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + kgce = energy_category_dict[energy_category_id]['kgce'] + kgco2e = energy_category_dict[energy_category_id]['kgco2e'] + + reporting[energy_category_id] = dict() + reporting[energy_category_id]['timestamps'] = list() + reporting[energy_category_id]['values_plan'] = list() + reporting[energy_category_id]['values_actual'] = list() + reporting[energy_category_id]['values_saving'] = list() + reporting[energy_category_id]['subtotal_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0) + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0) + # query reporting period's energy plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_tenant_input_category_hourly " + " WHERE tenant_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (tenant['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_tenant_hourly = cursor_energy_plan.fetchall() + + rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_tenant_periodically in rows_tenant_periodically: + current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + plan_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1] + reporting[energy_category_id]['timestamps'].append(current_datetime) + reporting[energy_category_id]['values_plan'].append(plan_value) + reporting[energy_category_id]['subtotal_plan'] += plan_value + reporting[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e + + # query reporting period's energy actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_tenant_input_category_hourly " + " WHERE tenant_id = %s " + " AND energy_category_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (tenant['id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_tenant_hourly = cursor_energy.fetchall() + + rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + for row_tenant_periodically in rows_tenant_periodically: + current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1] + reporting[energy_category_id]['values_actual'].append(actual_value) + reporting[energy_category_id]['subtotal_actual'] += actual_value + reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e + + # calculate reporting period's energy savings + for i in range(len(reporting[energy_category_id]['values_plan'])): + reporting[energy_category_id]['values_saving'].append( + reporting[energy_category_id]['values_plan'][i] - + reporting[energy_category_id]['values_actual'][i]) + + reporting[energy_category_id]['subtotal_saving'] = \ + reporting[energy_category_id]['subtotal_plan'] - \ + reporting[energy_category_id]['subtotal_actual'] + reporting[energy_category_id]['subtotal_in_kgce_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgce_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgce_actual'] + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \ + reporting[energy_category_id]['subtotal_in_kgco2e_plan'] - \ + reporting[energy_category_id]['subtotal_in_kgco2e_actual'] + ################################################################################################################ + # Step 8: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0 \ + and not is_quick_mode: + for energy_category_id in energy_category_set: + energy_category_tariff_dict = utilities.get_energy_category_tariffs(tenant['cost_center_id'], + energy_category_id, + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in energy_category_tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19][0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append(_('Tariff') + '-' + energy_category_dict[energy_category_id]['name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 9: query associated sensors and points data + ################################################################################################################ + if not is_quick_mode: + for point in point_list: + point_values = [] + point_timestamps = [] + if point['object_type'] == 'ENERGY_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_energy_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'ANALOG_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_analog_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + elif point['object_type'] == 'DIGITAL_VALUE': + query = (" SELECT utc_date_time, actual_value " + " FROM tbl_digital_value " + " WHERE point_id = %s " + " AND utc_date_time BETWEEN %s AND %s " + " ORDER BY utc_date_time ") + cursor_historical.execute(query, (point['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows = cursor_historical.fetchall() + + if rows is not None and len(rows) > 0: + for row in rows: + current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + point_timestamps.append(current_datetime) + point_values.append(row[1]) + + parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')') + parameters_data['timestamps'].append(point_timestamps) + parameters_data['values'].append(point_values) + + ################################################################################################################ + # Step 10: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + if cursor_historical: + cursor_historical.close() + if cnx_historical: + cnx_historical.close() + + result = dict() + + result['tenant'] = dict() + result['tenant']['name'] = tenant['name'] + result['tenant']['area'] = tenant['area'] + + result['base_period'] = dict() + result['base_period']['names'] = list() + result['base_period']['units'] = list() + result['base_period']['timestamps'] = list() + result['base_period']['values_saving'] = list() + result['base_period']['subtotals_saving'] = list() + result['base_period']['subtotals_in_kgce_saving'] = list() + result['base_period']['subtotals_in_kgco2e_saving'] = list() + result['base_period']['total_in_kgce_saving'] = Decimal(0.0) + result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0) + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['base_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['base_period']['timestamps'].append(base[energy_category_id]['timestamps']) + result['base_period']['values_saving'].append(base[energy_category_id]['values_saving']) + result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving']) + result['base_period']['subtotals_in_kgce_saving'].append( + base[energy_category_id]['subtotal_in_kgce_saving']) + result['base_period']['subtotals_in_kgco2e_saving'].append( + base[energy_category_id]['subtotal_in_kgco2e_saving']) + result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving'] + result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving'] + + result['reporting_period'] = dict() + result['reporting_period']['names'] = list() + result['reporting_period']['energy_category_ids'] = list() + result['reporting_period']['units'] = list() + result['reporting_period']['timestamps'] = list() + result['reporting_period']['values_saving'] = list() + result['reporting_period']['rates_saving'] = list() + result['reporting_period']['subtotals_saving'] = list() + result['reporting_period']['subtotals_in_kgce_saving'] = list() + result['reporting_period']['subtotals_in_kgco2e_saving'] = list() + result['reporting_period']['subtotals_per_unit_area_saving'] = list() + result['reporting_period']['increment_rates_saving'] = list() + result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0) + result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0) + + if energy_category_set is not None and len(energy_category_set) > 0: + for energy_category_id in energy_category_set: + result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name']) + result['reporting_period']['energy_category_ids'].append(energy_category_id) + result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure']) + result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps']) + result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving']) + result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving']) + result['reporting_period']['subtotals_in_kgce_saving'].append( + reporting[energy_category_id]['subtotal_in_kgce_saving']) + result['reporting_period']['subtotals_in_kgco2e_saving'].append( + reporting[energy_category_id]['subtotal_in_kgco2e_saving']) + result['reporting_period']['subtotals_per_unit_area_saving'].append( + reporting[energy_category_id]['subtotal_saving'] / tenant['area'] + if tenant['area'] != Decimal(0.0) else None) + result['reporting_period']['increment_rates_saving'].append( + (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) / + base[energy_category_id]['subtotal_saving'] + if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None) + result['reporting_period']['total_in_kgce_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgce_saving'] + result['reporting_period']['total_in_kgco2e_saving'] += \ + reporting[energy_category_id]['subtotal_in_kgco2e_saving'] + + rate = list() + for index, value in enumerate(reporting[energy_category_id]['values_saving']): + if index < len(base[energy_category_id]['values_saving']) \ + and base[energy_category_id]['values_saving'][index] != Decimal(0.0) \ + and value != Decimal(0.0): + rate.append((value - base[energy_category_id]['values_saving'][index]) + / base[energy_category_id]['values_saving'][index]) + else: + rate.append(None) + result['reporting_period']['rates_saving'].append(rate) + + result['reporting_period']['total_in_kgco2e_per_unit_area_saving'] = \ + result['reporting_period']['total_in_kgce_saving'] / tenant['area'] \ + if tenant['area'] != Decimal(0.0) else None + + result['reporting_period']['increment_rate_in_kgce_saving'] = \ + (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \ + result['base_period']['total_in_kgce_saving'] \ + if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None + + result['reporting_period']['total_in_kgce_per_unit_area_saving'] = \ + result['reporting_period']['total_in_kgco2e_saving'] / tenant['area'] if tenant['area'] > 0.0 else None + + result['reporting_period']['increment_rate_in_kgco2e_saving'] = \ + (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \ + result['base_period']['total_in_kgco2e_saving'] \ + if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None + + result['parameters'] = { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + } + + # export result to Excel file and then encode the file to base64 string + if not is_quick_mode: + result['excel_bytes_base64'] = excelexporters.tenantsaving.export(result, + tenant['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result) diff --git a/myems-api/reports/virtualmeterplan.py b/myems-api/reports/virtualmeterplan.py new file mode 100644 index 0000000000000000000000000000000000000000..54432a7167f870b9da67f679cddf60e86abbee42 --- /dev/null +++ b/myems-api/reports/virtualmeterplan.py @@ -0,0 +1,520 @@ +import re +from datetime import datetime, timedelta, timezone +from decimal import Decimal +import falcon +import mysql.connector +import simplejson as json +import config +import excelexporters.virtualmetersaving +from core import utilities +from core.useractivity import access_control, api_key_control + + +class Reporting: + @staticmethod + def __init__(): + """"Initializes Reporting""" + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + #################################################################################################################### + # PROCEDURES + # Step 1: valid parameters + # Step 2: query the virtual meter and energy category + # Step 3: query base period energy saving + # Step 4: query reporting period energy saving + # Step 5: query tariff data + # Step 6: construct the report + #################################################################################################################### + @staticmethod + def on_get(req, resp): + if 'API-KEY' not in req.headers or \ + not isinstance(req.headers['API-KEY'], str) or \ + len(str.strip(req.headers['API-KEY'])) == 0: + access_control(req) + else: + api_key_control(req) + print(req.params) + virtual_meter_id = req.params.get('virtualmeterid') + virtual_meter_uuid = req.params.get('virtualmeteruuid') + period_type = req.params.get('periodtype') + base_period_start_datetime_local = req.params.get('baseperiodstartdatetime') + base_period_end_datetime_local = req.params.get('baseperiodenddatetime') + reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime') + reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime') + language = req.params.get('language') + quick_mode = req.params.get('quickmode') + + ################################################################################################################ + # Step 1: valid parameters + ################################################################################################################ + if virtual_meter_id is None and virtual_meter_uuid is None: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_VIRTUAL_METER_ID') + + if virtual_meter_id is not None: + virtual_meter_id = str.strip(virtual_meter_id) + if not virtual_meter_id.isdigit() or int(virtual_meter_id) <= 0: + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_VIRTUAL_METER_ID') + + if virtual_meter_uuid is not None: + regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I) + match = regex.match(str.strip(virtual_meter_uuid)) + if not bool(match): + raise falcon.HTTPError(status=falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_VIRTUAL_METER_UUID') + + if period_type is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + else: + period_type = str.strip(period_type) + if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_PERIOD_TYPE') + + timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6]) + if config.utc_offset[0] == '-': + timezone_offset = -timezone_offset + + base_start_datetime_utc = None + if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0: + base_period_start_datetime_local = str.strip(base_period_start_datetime_local) + try: + base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_START_DATETIME") + base_start_datetime_utc = \ + base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + base_end_datetime_utc = None + if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0: + base_period_end_datetime_local = str.strip(base_period_end_datetime_local) + try: + base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_BASE_PERIOD_END_DATETIME") + base_end_datetime_utc = \ + base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + + if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \ + base_start_datetime_utc >= base_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_BASE_PERIOD_END_DATETIME') + + if reporting_period_start_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + else: + reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local) + try: + reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_START_DATETIME") + reporting_start_datetime_utc = \ + reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset) + # nomalize the start datetime + if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0) + else: + reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0) + + if reporting_period_end_datetime_local is None: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + else: + reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local) + try: + reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local, + '%Y-%m-%dT%H:%M:%S') + except ValueError: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description="API.INVALID_REPORTING_PERIOD_END_DATETIME") + reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \ + timedelta(minutes=timezone_offset) + + if reporting_start_datetime_utc >= reporting_end_datetime_utc: + raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_REPORTING_PERIOD_END_DATETIME') + + # if turn quick mode on, do not return parameters data and excel file + is_quick_mode = False + if quick_mode is not None and \ + len(str.strip(quick_mode)) > 0 and \ + str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'): + is_quick_mode = True + + trans = utilities.get_translation(language) + trans.install() + _ = trans.gettext + + ################################################################################################################ + # Step 2: query the virtual meter and energy category + ################################################################################################################ + cnx_system = mysql.connector.connect(**config.myems_system_db) + cursor_system = cnx_system.cursor() + + cnx_energy = mysql.connector.connect(**config.myems_energy_db) + cursor_energy = cnx_energy.cursor() + + cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db) + cursor_energy_plan = cnx_energy_plan.cursor() + + cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, " + " ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e " + " FROM tbl_virtual_meters m, tbl_energy_categories ec " + " WHERE m.id = %s AND m.energy_category_id = ec.id ", (virtual_meter_id,)) + row_virtual_meter = cursor_system.fetchone() + if row_virtual_meter is None: + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', + description='API.VIRTUAL_METER_NOT_FOUND') + + virtual_meter = dict() + virtual_meter['id'] = row_virtual_meter[0] + virtual_meter['name'] = row_virtual_meter[1] + virtual_meter['cost_center_id'] = row_virtual_meter[2] + virtual_meter['energy_category_id'] = row_virtual_meter[3] + virtual_meter['energy_category_name'] = row_virtual_meter[4] + virtual_meter['unit_of_measure'] = row_virtual_meter[5] + virtual_meter['kgce'] = row_virtual_meter[6] + virtual_meter['kgco2e'] = row_virtual_meter[7] + + ################################################################################################################ + # Step 3: query base period energy saving + ################################################################################################################ + base = dict() + base['timestamps'] = list() + base['values_plan'] = list() + base['values_actual'] = list() + base['values_saving'] = list() + base['total_in_category_plan'] = Decimal(0.0) + base['total_in_category_actual'] = Decimal(0.0) + base['total_in_category_saving'] = Decimal(0.0) + base['total_in_kgce_plan'] = Decimal(0.0) + base['total_in_kgce_actual'] = Decimal(0.0) + base['total_in_kgce_saving'] = Decimal(0.0) + base['total_in_kgco2e_plan'] = Decimal(0.0) + base['total_in_kgco2e_actual'] = Decimal(0.0) + base['total_in_kgco2e_saving'] = Decimal(0.0) + + # query base period plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_virtual_meter_hourly " + " WHERE virtual_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (virtual_meter['id'], + base_start_datetime_utc, + base_end_datetime_utc)) + rows_virtual_meter_hourly = cursor_energy_plan.fetchall() + + rows_virtual_meter_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + + for row_virtual_meter_periodically in rows_virtual_meter_periodically: + current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ + else row_virtual_meter_periodically[1] + base['timestamps'].append(current_datetime) + base['values_plan'].append(actual_value) + base['total_in_category_plan'] += actual_value + base['total_in_kgce_plan'] += actual_value * virtual_meter['kgce'] + base['total_in_kgco2e_plan'] += actual_value * virtual_meter['kgco2e'] + + # query base period actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_virtual_meter_hourly " + " WHERE virtual_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (virtual_meter['id'], + base_start_datetime_utc, + base_end_datetime_utc)) + rows_virtual_meter_hourly = cursor_energy.fetchall() + + rows_virtual_meter_periodically = \ + utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, + base_start_datetime_utc, + base_end_datetime_utc, + period_type) + + for row_virtual_meter_periodically in rows_virtual_meter_periodically: + current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ + else row_virtual_meter_periodically[1] + base['values_actual'].append(actual_value) + base['total_in_category_actual'] += actual_value + base['total_in_kgce_actual'] += actual_value * virtual_meter['kgce'] + base['total_in_kgco2e_actual'] += actual_value * virtual_meter['kgco2e'] + + # calculate base period saving + for i in range(len(base['values_plan'])): + base['values_saving'].append(base['values_plan'][i] - base['values_actual'][i]) + + base['total_in_category_saving'] = base['total_in_category_plan'] - base['total_in_category_actual'] + base['total_in_kgce_saving'] = base['total_in_kgce_plan'] - base['total_in_kgce_actual'] + base['total_in_kgco2e_saving'] = base['total_in_kgco2e_plan'] - base['total_in_kgco2e_actual'] + + ################################################################################################################ + # Step 3: query reporting period energy saving + ################################################################################################################ + reporting = dict() + reporting['timestamps'] = list() + reporting['values_plan'] = list() + reporting['values_actual'] = list() + reporting['values_saving'] = list() + reporting['values_rates'] = list() + reporting['total_in_category_plan'] = Decimal(0.0) + reporting['total_in_category_actual'] = Decimal(0.0) + reporting['total_in_category_saving'] = Decimal(0.0) + reporting['total_in_kgce_plan'] = Decimal(0.0) + reporting['total_in_kgce_actual'] = Decimal(0.0) + reporting['total_in_kgce_saving'] = Decimal(0.0) + reporting['total_in_kgco2e_plan'] = Decimal(0.0) + reporting['total_in_kgco2e_actual'] = Decimal(0.0) + reporting['total_in_kgco2e_saving'] = Decimal(0.0) + # query reporting period plan + cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_virtual_meter_hourly " + " WHERE virtual_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (virtual_meter['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_virtual_meter_hourly = cursor_energy_plan.fetchall() + + rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_virtual_meter_periodically in rows_virtual_meter_periodically: + current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ + else row_virtual_meter_periodically[1] + + reporting['timestamps'].append(current_datetime) + reporting['values_plan'].append(actual_value) + reporting['total_in_category_plan'] += actual_value + reporting['total_in_kgce_plan'] += actual_value * virtual_meter['kgce'] + reporting['total_in_kgco2e_plan'] += actual_value * virtual_meter['kgco2e'] + + # query reporting period actual + cursor_energy.execute(" SELECT start_datetime_utc, actual_value " + " FROM tbl_virtual_meter_hourly " + " WHERE virtual_meter_id = %s " + " AND start_datetime_utc >= %s " + " AND start_datetime_utc < %s " + " ORDER BY start_datetime_utc ", + (virtual_meter['id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc)) + rows_virtual_meter_hourly = cursor_energy.fetchall() + + rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly, + reporting_start_datetime_utc, + reporting_end_datetime_utc, + period_type) + + for row_virtual_meter_periodically in rows_virtual_meter_periodically: + current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \ + timedelta(minutes=timezone_offset) + if period_type == 'hourly': + current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S') + elif period_type == 'daily': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'weekly': + current_datetime = current_datetime_local.strftime('%Y-%m-%d') + elif period_type == 'monthly': + current_datetime = current_datetime_local.strftime('%Y-%m') + elif period_type == 'yearly': + current_datetime = current_datetime_local.strftime('%Y') + + actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \ + else row_virtual_meter_periodically[1] + + reporting['values_actual'].append(actual_value) + reporting['total_in_category_actual'] += actual_value + reporting['total_in_kgce_actual'] += actual_value * virtual_meter['kgce'] + reporting['total_in_kgco2e_actual'] += actual_value * virtual_meter['kgco2e'] + + # calculate base period saving + for i in range(len(reporting['values_plan'])): + reporting['values_saving'].append(reporting['values_plan'][i] - reporting['values_actual'][i]) + + reporting['total_in_category_saving'] = \ + reporting['total_in_category_plan'] - reporting['total_in_category_actual'] + reporting['total_in_kgce_saving'] = \ + reporting['total_in_kgce_plan'] - reporting['total_in_kgce_actual'] + reporting['total_in_kgco2e_saving'] = \ + reporting['total_in_kgco2e_plan'] - reporting['total_in_kgco2e_actual'] + + for index, value in enumerate(reporting['values_saving']): + if index < len(base['values_saving']) and base['values_saving'][index] != 0 and value != 0: + reporting['values_rates'].append((value - base['values_saving'][index]) / base['values_saving'][index]) + else: + reporting['values_rates'].append(None) + + ################################################################################################################ + # Step 5: query tariff data + ################################################################################################################ + parameters_data = dict() + parameters_data['names'] = list() + parameters_data['timestamps'] = list() + parameters_data['values'] = list() + if config.is_tariff_appended and not is_quick_mode: + tariff_dict = utilities.get_energy_category_tariffs(virtual_meter['cost_center_id'], + virtual_meter['energy_category_id'], + reporting_start_datetime_utc, + reporting_end_datetime_utc) + tariff_timestamp_list = list() + tariff_value_list = list() + for k, v in tariff_dict.items(): + # convert k from utc to local + k = k + timedelta(minutes=timezone_offset) + tariff_timestamp_list.append(k.isoformat()[0:19]) + tariff_value_list.append(v) + + parameters_data['names'].append(_('Tariff') + '-' + virtual_meter['energy_category_name']) + parameters_data['timestamps'].append(tariff_timestamp_list) + parameters_data['values'].append(tariff_value_list) + + ################################################################################################################ + # Step 6: construct the report + ################################################################################################################ + if cursor_system: + cursor_system.close() + if cnx_system: + cnx_system.close() + + if cursor_energy: + cursor_energy.close() + if cnx_energy: + cnx_energy.close() + + if cursor_energy_plan: + cursor_energy_plan.close() + if cnx_energy_plan: + cnx_energy_plan.close() + + result = { + "virtual_meter": { + "cost_center_id": virtual_meter['cost_center_id'], + "energy_category_id": virtual_meter['energy_category_id'], + "energy_category_name": virtual_meter['energy_category_name'], + "unit_of_measure": virtual_meter['unit_of_measure'], + "kgce": virtual_meter['kgce'], + "kgco2e": virtual_meter['kgco2e'], + }, + "base_period": { + "total_in_category_saving": base['total_in_category_saving'], + "total_in_kgce_saving": base['total_in_kgce_saving'], + "total_in_kgco2e_saving": base['total_in_kgco2e_saving'], + "timestamps": base['timestamps'], + "values_saving": base['values_saving'], + }, + "reporting_period": { + "increment_rate_saving": + (reporting['total_in_category_saving'] - base['total_in_category_saving']) / + base['total_in_category_saving'] + if base['total_in_category_saving'] != Decimal(0.0) else None, + "total_in_category_saving": reporting['total_in_category_saving'], + "total_in_kgce_saving": reporting['total_in_kgce_saving'], + "total_in_kgco2e_saving": reporting['total_in_kgco2e_saving'], + "timestamps": reporting['timestamps'], + "values_saving": reporting['values_saving'], + "values_rates": reporting['values_rates'], + }, + "parameters": { + "names": parameters_data['names'], + "timestamps": parameters_data['timestamps'], + "values": parameters_data['values'] + }, + } + + # export result to Excel file and then encode the file to base64 string + if not is_quick_mode: + result['excel_bytes_base64'] = \ + excelexporters.virtualmetersaving.export(result, + virtual_meter['name'], + base_period_start_datetime_local, + base_period_end_datetime_local, + reporting_period_start_datetime_local, + reporting_period_end_datetime_local, + period_type, + language) + + resp.text = json.dumps(result)