chellow 1729269304.0.0__py3-none-any.whl → 1729760318.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of chellow might be problematic. Click here for more details.
- chellow/e/hh_importer.py +9 -1
- chellow/e/hh_parser_schneider_csv.py +75 -0
- chellow/e/hh_parser_schneider_xlsx.py +118 -0
- chellow/e/views.py +2 -140
- chellow/reports/report_g_monthly_duration.py +234 -153
- chellow/templates/base.html +1 -1
- chellow/templates/e/channel_snags.html +1 -1
- chellow/templates/home.html +1 -1
- chellow/templates/scenario.html +61 -0
- chellow/templates/scenario_add.html +21 -0
- chellow/templates/scenario_docs.html +256 -0
- chellow/templates/scenario_edit.html +35 -0
- chellow/templates/scenarios.html +39 -0
- chellow/views.py +140 -0
- {chellow-1729269304.0.0.dist-info → chellow-1729760318.0.0.dist-info}/METADATA +1 -1
- {chellow-1729269304.0.0.dist-info → chellow-1729760318.0.0.dist-info}/RECORD +17 -10
- {chellow-1729269304.0.0.dist-info → chellow-1729760318.0.0.dist-info}/WHEEL +0 -0
chellow/e/hh_importer.py
CHANGED
|
@@ -40,7 +40,15 @@ from chellow.utils import (
|
|
|
40
40
|
processes = defaultdict(list)
|
|
41
41
|
tasks = {}
|
|
42
42
|
|
|
43
|
-
extensions = [
|
|
43
|
+
extensions = [
|
|
44
|
+
".df2",
|
|
45
|
+
".simple.csv",
|
|
46
|
+
".bg.csv",
|
|
47
|
+
".vital.xlsx",
|
|
48
|
+
".edf.csv",
|
|
49
|
+
".schneider.csv",
|
|
50
|
+
".schneider.xlsx",
|
|
51
|
+
]
|
|
44
52
|
|
|
45
53
|
|
|
46
54
|
class HhDataImportProcess(threading.Thread):
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import itertools
|
|
3
|
+
from codecs import iterdecode
|
|
4
|
+
from datetime import datetime as Datetime
|
|
5
|
+
from decimal import Decimal, InvalidOperation
|
|
6
|
+
|
|
7
|
+
from werkzeug.exceptions import BadRequest
|
|
8
|
+
|
|
9
|
+
from chellow.utils import parse_mpan_core, to_utc, validate_hh_start
|
|
10
|
+
|
|
11
|
+
# "Time stamp","Value","Events","Comment","User"
|
|
12
|
+
# "18/10/2024 09:30:00","88.0","","",""
|
|
13
|
+
# "18/10/2024 09:00:00","89.1","","",""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_parser(reader, mpan_map, messages):
|
|
17
|
+
return HhParserSchneiderCsv(reader, mpan_map, messages)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_field(values, index, name):
|
|
21
|
+
if len(values) > index:
|
|
22
|
+
return values[index].strip()
|
|
23
|
+
else:
|
|
24
|
+
raise BadRequest(f"Can't find field {index}, {name}.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_values(values):
|
|
28
|
+
start_date_str = get_field(values, 0, "Timestamp")
|
|
29
|
+
start_date = validate_hh_start(
|
|
30
|
+
to_utc(Datetime.strptime(start_date_str, "%d/%m/%Y %H:%M:%S"))
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
reading_str = get_field(values, 1, "Reading")
|
|
34
|
+
reading_str = reading_str.replace(",", "")
|
|
35
|
+
try:
|
|
36
|
+
reading = Decimal(reading_str)
|
|
37
|
+
except InvalidOperation as e:
|
|
38
|
+
raise BadRequest(f"Problem parsing the number {reading_str}. {e}")
|
|
39
|
+
return start_date, reading
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class HhParserSchneiderCsv:
|
|
43
|
+
def __init__(self, reader, mpan_map, messages):
|
|
44
|
+
s = iterdecode(reader, "utf-8")
|
|
45
|
+
self.shredder = zip(itertools.count(1), csv.reader(s))
|
|
46
|
+
next(self.shredder) # skip the title line
|
|
47
|
+
self.line_number, self.values = next(self.shredder)
|
|
48
|
+
_, self.pres_reading = parse_values(self.values)
|
|
49
|
+
self.mpan_core = parse_mpan_core(next(iter(mpan_map.values())))
|
|
50
|
+
|
|
51
|
+
def __iter__(self):
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def __next__(self):
|
|
55
|
+
try:
|
|
56
|
+
self.line_number, self.values = next(self.shredder)
|
|
57
|
+
start_date, reading = parse_values(self.values)
|
|
58
|
+
datum = {
|
|
59
|
+
"mpan_core": self.mpan_core,
|
|
60
|
+
"channel_type": "ACTIVE",
|
|
61
|
+
"start_date": start_date,
|
|
62
|
+
"value": self.pres_reading - reading,
|
|
63
|
+
"status": "A",
|
|
64
|
+
}
|
|
65
|
+
self.pres_reading = reading
|
|
66
|
+
return datum
|
|
67
|
+
except BadRequest as e:
|
|
68
|
+
e.description = (
|
|
69
|
+
f"Problem at line number: {self.line_number}: {self.values}: "
|
|
70
|
+
f"{e.description}"
|
|
71
|
+
)
|
|
72
|
+
raise e
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
self.shredder.close()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from datetime import datetime as Datetime
|
|
2
|
+
from decimal import Decimal, InvalidOperation
|
|
3
|
+
|
|
4
|
+
from openpyxl import load_workbook
|
|
5
|
+
|
|
6
|
+
from werkzeug.exceptions import BadRequest
|
|
7
|
+
|
|
8
|
+
from chellow.utils import parse_mpan_core, to_ct, to_utc, validate_hh_start
|
|
9
|
+
|
|
10
|
+
# "Time stamp","Value","Events","Comment","User"
|
|
11
|
+
# "18/10/2024 09:30:00","88.0","","",""
|
|
12
|
+
# "18/10/2024 09:00:00","89.1","","",""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_parser(reader, mpan_map, messages):
|
|
16
|
+
return HhParserSchneiderXlsx(reader, mpan_map, messages)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_cell(sheet, col, row):
|
|
20
|
+
try:
|
|
21
|
+
coordinates = f"{col}{row}"
|
|
22
|
+
return sheet[coordinates]
|
|
23
|
+
except IndexError:
|
|
24
|
+
raise BadRequest(f"Can't find the cell {coordinates} on sheet {sheet}.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_date(sheet, col, row):
|
|
28
|
+
cell = get_cell(sheet, col, row)
|
|
29
|
+
val = cell.value
|
|
30
|
+
if not isinstance(val, Datetime):
|
|
31
|
+
raise BadRequest(
|
|
32
|
+
f"Problem reading {val} (of type {type(val)}) as a timestamp at "
|
|
33
|
+
f"{cell.coordinate}."
|
|
34
|
+
)
|
|
35
|
+
return val
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_str(sheet, col, row):
|
|
39
|
+
return get_cell(sheet, col, row).value.strip()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_dec(sheet, col, row):
|
|
43
|
+
return get_dec_from_cell(get_cell(sheet, col, row))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_dec_from_cell(cell):
|
|
47
|
+
try:
|
|
48
|
+
return Decimal(str(cell.value))
|
|
49
|
+
except InvalidOperation as e:
|
|
50
|
+
raise BadRequest(f"Problem parsing the number at {cell.coordinate}. {e}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_int(sheet, col, row):
|
|
54
|
+
return int(get_cell(sheet, col, row).value)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def find_hhs(sheet, set_line_number, mpan_core):
|
|
58
|
+
pres_reading = None
|
|
59
|
+
for row in range(2, len(sheet["A"]) + 1):
|
|
60
|
+
set_line_number(row)
|
|
61
|
+
try:
|
|
62
|
+
timestamp_cell = get_cell(sheet, "A", row)
|
|
63
|
+
timestamp_str = timestamp_cell.value
|
|
64
|
+
if timestamp_str is None:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
# 2024-10-02 00:00:00 +1H, DST
|
|
68
|
+
ts_str = timestamp_str.split(",")[0]
|
|
69
|
+
ts_naive = Datetime.strptime(ts_str[:19], "%Y-%m-%d %H:%M:%S")
|
|
70
|
+
# Sometimes has seconds
|
|
71
|
+
ts_naive = Datetime(
|
|
72
|
+
ts_naive.year,
|
|
73
|
+
ts_naive.month,
|
|
74
|
+
ts_naive.day,
|
|
75
|
+
ts_naive.hour,
|
|
76
|
+
ts_naive.minute,
|
|
77
|
+
)
|
|
78
|
+
is_dst = ts_str[21] == 1
|
|
79
|
+
if is_dst:
|
|
80
|
+
start_date = to_utc(to_ct(ts_naive))
|
|
81
|
+
else:
|
|
82
|
+
start_date = to_utc(ts_naive)
|
|
83
|
+
start_date = validate_hh_start(start_date)
|
|
84
|
+
|
|
85
|
+
reading = get_dec(sheet, "B", row)
|
|
86
|
+
|
|
87
|
+
if pres_reading is not None:
|
|
88
|
+
value = pres_reading - reading
|
|
89
|
+
if value >= 0:
|
|
90
|
+
yield {
|
|
91
|
+
"mpan_core": mpan_core,
|
|
92
|
+
"channel_type": "ACTIVE",
|
|
93
|
+
"start_date": start_date,
|
|
94
|
+
"value": value,
|
|
95
|
+
"status": "A",
|
|
96
|
+
}
|
|
97
|
+
pres_reading = reading
|
|
98
|
+
except BadRequest as e:
|
|
99
|
+
e.description = f"Problem at line number: {row}: {e.description}"
|
|
100
|
+
raise e
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class HhParserSchneiderXlsx:
|
|
104
|
+
def __init__(self, input_stream, mpan_map, messages):
|
|
105
|
+
book = load_workbook(input_stream, data_only=True)
|
|
106
|
+
self.sheet = book.worksheets[0]
|
|
107
|
+
imp_name = get_str(self.sheet, "B", 1).strip()
|
|
108
|
+
self.mpan_core = parse_mpan_core(mpan_map[imp_name])
|
|
109
|
+
self.line_number = 0
|
|
110
|
+
|
|
111
|
+
def _set_line_number(self, line_number):
|
|
112
|
+
self.line_number = line_number
|
|
113
|
+
|
|
114
|
+
def __iter__(self):
|
|
115
|
+
return find_hhs(self.sheet, self._set_line_number, self.mpan_core)
|
|
116
|
+
|
|
117
|
+
def close(self):
|
|
118
|
+
self.shredder.close()
|
chellow/e/views.py
CHANGED
|
@@ -68,7 +68,6 @@ from chellow.models import (
|
|
|
68
68
|
ReadType,
|
|
69
69
|
RegisterRead,
|
|
70
70
|
Report,
|
|
71
|
-
Scenario,
|
|
72
71
|
Site,
|
|
73
72
|
SiteEra,
|
|
74
73
|
Snag,
|
|
@@ -80,7 +79,6 @@ from chellow.models import (
|
|
|
80
79
|
)
|
|
81
80
|
from chellow.utils import (
|
|
82
81
|
HH,
|
|
83
|
-
c_months_c,
|
|
84
82
|
c_months_u,
|
|
85
83
|
csv_make_val,
|
|
86
84
|
ct_datetime,
|
|
@@ -1220,7 +1218,7 @@ def dc_contract_edit_post(contract_id):
|
|
|
1220
1218
|
|
|
1221
1219
|
|
|
1222
1220
|
@e.route("/dc_contracts/<int:contract_id>/hh_imports")
|
|
1223
|
-
def
|
|
1221
|
+
def dc_contract_hh_imports_get(contract_id):
|
|
1224
1222
|
contract = Contract.get_dc_by_id(g.sess, contract_id)
|
|
1225
1223
|
processes = chellow.e.hh_importer.get_hh_import_processes(contract.id)
|
|
1226
1224
|
return render_template(
|
|
@@ -1232,7 +1230,7 @@ def dc_contracts_hh_imports_get(contract_id):
|
|
|
1232
1230
|
|
|
1233
1231
|
|
|
1234
1232
|
@e.route("/dc_contracts/<int:contract_id>/hh_imports", methods=["POST"])
|
|
1235
|
-
def
|
|
1233
|
+
def dc_contract_hh_imports_post(contract_id):
|
|
1236
1234
|
try:
|
|
1237
1235
|
contract = Contract.get_dc_by_id(g.sess, contract_id)
|
|
1238
1236
|
|
|
@@ -3910,142 +3908,6 @@ def read_type_get(read_type_id):
|
|
|
3910
3908
|
return render_template("read_type.html", read_type=read_type)
|
|
3911
3909
|
|
|
3912
3910
|
|
|
3913
|
-
@e.route("/scenarios")
|
|
3914
|
-
def scenarios_get():
|
|
3915
|
-
scenarios = g.sess.query(Scenario).order_by(Scenario.name).all()
|
|
3916
|
-
return render_template("scenarios.html", scenarios=scenarios)
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
@e.route("/scenarios/add", methods=["POST"])
|
|
3920
|
-
def scenario_add_post():
|
|
3921
|
-
try:
|
|
3922
|
-
name = req_str("name")
|
|
3923
|
-
properties = req_zish("properties")
|
|
3924
|
-
scenario = Scenario.insert(g.sess, name, properties)
|
|
3925
|
-
g.sess.commit()
|
|
3926
|
-
return chellow_redirect(f"/scenarios/{scenario.id}", 303)
|
|
3927
|
-
except BadRequest as e:
|
|
3928
|
-
g.sess.rollback()
|
|
3929
|
-
flash(e.description)
|
|
3930
|
-
scenarios = g.sess.query(Scenario).order_by(Scenario.name)
|
|
3931
|
-
return make_response(
|
|
3932
|
-
render_template("scenario_add.html", scenarios=scenarios), 400
|
|
3933
|
-
)
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
@e.route("/scenarios/add")
|
|
3937
|
-
def scenario_add_get():
|
|
3938
|
-
now = utc_datetime_now()
|
|
3939
|
-
props = {
|
|
3940
|
-
"scenario_start_month": now.month,
|
|
3941
|
-
"scenario_start_year": now.year,
|
|
3942
|
-
"scenario_duration": 1,
|
|
3943
|
-
}
|
|
3944
|
-
return render_template("scenario_add.html", initial_props=dumps(props))
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
@e.route("/scenarios/<int:scenario_id>")
|
|
3948
|
-
def scenario_get(scenario_id):
|
|
3949
|
-
start_date = None
|
|
3950
|
-
finish_date = None
|
|
3951
|
-
duration = 1
|
|
3952
|
-
|
|
3953
|
-
scenario = Scenario.get_by_id(g.sess, scenario_id)
|
|
3954
|
-
props = scenario.props
|
|
3955
|
-
site_codes = "\n".join(props.get("site_codes", []))
|
|
3956
|
-
try:
|
|
3957
|
-
duration = props["scenario_duration"]
|
|
3958
|
-
_, finish_date_ct = list(
|
|
3959
|
-
c_months_c(
|
|
3960
|
-
start_year=props["scenario_start_year"],
|
|
3961
|
-
start_month=props["scenario_start_month"],
|
|
3962
|
-
months=duration,
|
|
3963
|
-
)
|
|
3964
|
-
)[-1]
|
|
3965
|
-
finish_date = to_utc(finish_date_ct)
|
|
3966
|
-
except KeyError:
|
|
3967
|
-
pass
|
|
3968
|
-
|
|
3969
|
-
try:
|
|
3970
|
-
start_date = to_utc(
|
|
3971
|
-
ct_datetime(
|
|
3972
|
-
props["scenario_start_year"],
|
|
3973
|
-
props["scenario_start_month"],
|
|
3974
|
-
props["scenario_start_day"],
|
|
3975
|
-
props["scenario_start_hour"],
|
|
3976
|
-
props["scenario_start_minute"],
|
|
3977
|
-
)
|
|
3978
|
-
)
|
|
3979
|
-
except KeyError:
|
|
3980
|
-
pass
|
|
3981
|
-
|
|
3982
|
-
try:
|
|
3983
|
-
finish_date = to_utc(
|
|
3984
|
-
ct_datetime(
|
|
3985
|
-
props["scenario_finish_year"],
|
|
3986
|
-
props["scenario_finish_month"],
|
|
3987
|
-
props["scenario_finish_day"],
|
|
3988
|
-
props["scenario_finish_hour"],
|
|
3989
|
-
props["scenario_finish_minute"],
|
|
3990
|
-
)
|
|
3991
|
-
)
|
|
3992
|
-
except KeyError:
|
|
3993
|
-
pass
|
|
3994
|
-
return render_template(
|
|
3995
|
-
"scenario.html",
|
|
3996
|
-
scenario=scenario,
|
|
3997
|
-
scenario_start_date=start_date,
|
|
3998
|
-
scenario_finish_date=finish_date,
|
|
3999
|
-
scenario_duration=duration,
|
|
4000
|
-
site_codes=site_codes,
|
|
4001
|
-
)
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
@e.route("/scenarios/<int:scenario_id>/edit")
|
|
4005
|
-
def scenario_edit_get(scenario_id):
|
|
4006
|
-
scenario = Scenario.get_by_id(g.sess, scenario_id)
|
|
4007
|
-
return render_template("scenario_edit.html", scenario=scenario)
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
@e.route("/scenarios/<int:scenario_id>/edit", methods=["POST"])
|
|
4011
|
-
def scenario_edit_post(scenario_id):
|
|
4012
|
-
try:
|
|
4013
|
-
scenario = Scenario.get_by_id(g.sess, scenario_id)
|
|
4014
|
-
name = req_str("name")
|
|
4015
|
-
properties = req_zish("properties")
|
|
4016
|
-
scenario.update(name, properties)
|
|
4017
|
-
g.sess.commit()
|
|
4018
|
-
return chellow_redirect(f"/scenarios/{scenario.id}", 303)
|
|
4019
|
-
except BadRequest as e:
|
|
4020
|
-
g.sess.rollback()
|
|
4021
|
-
description = e.description
|
|
4022
|
-
flash(description)
|
|
4023
|
-
return make_response(
|
|
4024
|
-
render_template(
|
|
4025
|
-
"scenario_edit.html",
|
|
4026
|
-
scenario=scenario,
|
|
4027
|
-
),
|
|
4028
|
-
400,
|
|
4029
|
-
)
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
@e.route("/scenarios/<int:scenario_id>/edit", methods=["DELETE"])
|
|
4033
|
-
def scenario_edit_delete(scenario_id):
|
|
4034
|
-
try:
|
|
4035
|
-
scenario = Scenario.get_by_id(g.sess, scenario_id)
|
|
4036
|
-
scenario.delete(g.sess)
|
|
4037
|
-
g.sess.commit()
|
|
4038
|
-
res = make_response()
|
|
4039
|
-
res.headers["HX-Redirect"] = f"{chellow.utils.url_root}/e/scenarios"
|
|
4040
|
-
return res
|
|
4041
|
-
except BadRequest as e:
|
|
4042
|
-
g.sess.rollback()
|
|
4043
|
-
flash(e.description)
|
|
4044
|
-
return make_response(
|
|
4045
|
-
render_template("scenario_edit.html", scenario=scenario), 400
|
|
4046
|
-
)
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
3911
|
@e.route("/sites/<int:site_id>/energy_management")
|
|
4050
3912
|
def site_energy_management_get(site_id):
|
|
4051
3913
|
site = Site.get_by_id(g.sess, site_id)
|