chellow 1715873476.0.0__py3-none-any.whl → 1716366171.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/edi_lib.py +27 -20
- chellow/gas/bill_parser_engie_edi.py +4 -2
- chellow/gas/bill_parser_total_edi.py +251 -0
- chellow/gas/engine.py +1 -10
- chellow/gas/views.py +37 -5
- chellow/templates/g/bill_import.html +24 -16
- chellow/templates/g/supply_edit.html +44 -58
- chellow/views.py +13 -15
- {chellow-1715873476.0.0.dist-info → chellow-1716366171.0.0.dist-info}/METADATA +1 -1
- {chellow-1715873476.0.0.dist-info → chellow-1716366171.0.0.dist-info}/RECORD +11 -10
- {chellow-1715873476.0.0.dist-info → chellow-1716366171.0.0.dist-info}/WHEEL +0 -0
chellow/edi_lib.py
CHANGED
|
@@ -6,54 +6,56 @@ from werkzeug.exceptions import BadRequest
|
|
|
6
6
|
from chellow.utils import ct_datetime, to_ct, to_utc
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
def line_iter(f):
|
|
10
|
+
line = []
|
|
11
|
+
while c := f.read(1) != "":
|
|
12
|
+
if c == "'":
|
|
13
|
+
yield "".join(line)
|
|
14
|
+
line.clear()
|
|
15
|
+
else:
|
|
16
|
+
line.append(c)
|
|
17
|
+
|
|
18
|
+
|
|
9
19
|
class EdiParser:
|
|
10
20
|
def __init__(self, f):
|
|
11
|
-
|
|
21
|
+
|
|
22
|
+
self.line_iterator = line_iter(f)
|
|
12
23
|
self.line_number = 0
|
|
13
24
|
|
|
14
25
|
def __iter__(self):
|
|
15
26
|
return self
|
|
16
27
|
|
|
17
28
|
def __next__(self):
|
|
18
|
-
self.line = next(self.
|
|
29
|
+
self.line = next(self.line_iterator).strip()
|
|
19
30
|
self.line_number += 1
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
raise BadRequest(
|
|
23
|
-
f"The parser expects each line to end with a ', but line number "
|
|
24
|
-
f"{self.line_number} doesn't: {self.line}."
|
|
25
|
-
)
|
|
26
|
-
self.elements = [element.split(":") for element in self.line[4:-1].split("+")]
|
|
32
|
+
self.elements = [element.split(":") for element in self.line[4].split("+")]
|
|
27
33
|
return self.line[:3]
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
def parse_edi(edi_str):
|
|
31
|
-
for line_number, raw_line in enumerate(edi_str.
|
|
32
|
-
if raw_line[-1] != "'":
|
|
33
|
-
raise BadRequest(
|
|
34
|
-
f"The parser expects each line to end with a ', but line "
|
|
35
|
-
f"number {line_number} doesn't: {raw_line}."
|
|
36
|
-
)
|
|
37
|
-
|
|
37
|
+
for line_number, raw_line in enumerate(edi_str.split("'"), start=1):
|
|
38
38
|
line = raw_line.strip()
|
|
39
|
+
if len(line) == 0:
|
|
40
|
+
continue
|
|
39
41
|
code = line[:3]
|
|
40
42
|
|
|
41
|
-
els = [el.split(":") for el in line[4
|
|
43
|
+
els = [el.split(":") for el in line[4:].split("+")]
|
|
42
44
|
|
|
43
|
-
segment_name = code + els[1][0] if code == "CCD" else code
|
|
45
|
+
segment_name = code + els[1][0].strip() if code == "CCD" else code
|
|
44
46
|
|
|
45
47
|
try:
|
|
46
48
|
elem_data = SEGMENTS[segment_name]
|
|
47
49
|
except KeyError:
|
|
48
50
|
raise BadRequest(
|
|
49
51
|
f"At line number {line_number} the segment name {segment_name} isn't "
|
|
50
|
-
f"recognized."
|
|
52
|
+
f"recognized. {raw_line}"
|
|
51
53
|
)
|
|
52
54
|
elem_codes = [m["code"] for m in elem_data["elements"]]
|
|
53
55
|
|
|
54
56
|
elements = dict(zip(elem_codes, els))
|
|
55
57
|
|
|
56
|
-
yield line_number, line, segment_name, elements
|
|
58
|
+
yield line_number, f"{line}'", segment_name, elements
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def to_decimal(components):
|
|
@@ -67,11 +69,16 @@ def to_decimal(components):
|
|
|
67
69
|
|
|
68
70
|
|
|
69
71
|
def to_ct_date(component):
|
|
72
|
+
if len(component) == 0:
|
|
73
|
+
return None
|
|
70
74
|
return to_ct(Datetime.strptime(component, "%y%m%d"))
|
|
71
75
|
|
|
72
76
|
|
|
73
77
|
def to_date(component):
|
|
74
|
-
|
|
78
|
+
dt = to_ct_date(component)
|
|
79
|
+
if dt is None:
|
|
80
|
+
return None
|
|
81
|
+
return to_utc(dt)
|
|
75
82
|
|
|
76
83
|
|
|
77
84
|
def to_finish_date(component):
|
|
@@ -301,8 +301,10 @@ class Parser:
|
|
|
301
301
|
func = CODE_FUNCS[seg_name]
|
|
302
302
|
except KeyError:
|
|
303
303
|
raise BadRequest(f"Code {seg_name} not recognized.")
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
try:
|
|
305
|
+
bill = func(elements, headers)
|
|
306
|
+
except BaseException as e:
|
|
307
|
+
raise BadRequest(f"Propblem with segment {line}: {e}") from e
|
|
306
308
|
|
|
307
309
|
if "raw_lines" in headers:
|
|
308
310
|
headers["raw_lines"].append(line)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from datetime import datetime as Datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from dateutil.relativedelta import relativedelta
|
|
6
|
+
|
|
7
|
+
from werkzeug.exceptions import BadRequest
|
|
8
|
+
|
|
9
|
+
from chellow.edi_lib import parse_edi, to_date, to_decimal
|
|
10
|
+
from chellow.utils import HH, to_ct, to_utc
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
READ_TYPE_MAP = {"00": "A", "01": "E", "02": "E"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SUPPLIER_CODE_MAP = {
|
|
17
|
+
"STD": ("standing", Decimal("1000"), Decimal("1")),
|
|
18
|
+
"MET": ("commodity", Decimal("100000"), Decimal("1000")),
|
|
19
|
+
"CCL": ("ccl", Decimal("100000"), Decimal("1000")),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
UNIT_MAP = {"M3": "M3", "HH": "HCUF", "HCUF": "HCUF"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _to_finish_date(date_str):
|
|
26
|
+
if len(date_str) == 0:
|
|
27
|
+
return None
|
|
28
|
+
return to_utc(
|
|
29
|
+
to_ct(Datetime.strptime(date_str, "%y%m%d") + relativedelta(days=1) - HH)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _process_ADJ(elements, headers):
|
|
34
|
+
adjf = elements["ADJF"]
|
|
35
|
+
if adjf[0] == "CV":
|
|
36
|
+
headers["cv"] = Decimal(adjf[1]) / Decimal(100000)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _process_BCD(elements, headers):
|
|
40
|
+
ivdt = elements["IVDT"]
|
|
41
|
+
headers["issue_date"] = to_date(ivdt[0])
|
|
42
|
+
|
|
43
|
+
invn = elements["INVN"]
|
|
44
|
+
headers["reference"] = invn[0]
|
|
45
|
+
|
|
46
|
+
btcd = elements["BTCD"]
|
|
47
|
+
headers["bill_type_code"] = btcd[0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _process_MHD(elements, headers):
|
|
51
|
+
headers.clear()
|
|
52
|
+
|
|
53
|
+
typ = elements["TYPE"]
|
|
54
|
+
message_type = headers["message_type"] = typ[0]
|
|
55
|
+
if message_type == "UTLBIL":
|
|
56
|
+
headers["reads"] = []
|
|
57
|
+
headers["raw_lines"] = []
|
|
58
|
+
headers["breakdown"] = defaultdict(int, {"units_consumed": Decimal(0)})
|
|
59
|
+
headers["kwh"] = Decimal("0.00")
|
|
60
|
+
headers["net"] = Decimal("0.00")
|
|
61
|
+
headers["vat"] = Decimal("0.00")
|
|
62
|
+
headers["gross"] = Decimal("0.00")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
NUCT_LOOKUP = {"DAY": "days", "KWH": "kwh"}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _process_CCD2(elements, headers):
|
|
69
|
+
breakdown = headers["breakdown"]
|
|
70
|
+
ccde = elements["CCDE"]
|
|
71
|
+
nuct = elements["NUCT"]
|
|
72
|
+
mtnr = elements["MTNR"]
|
|
73
|
+
conb = elements["CONB"]
|
|
74
|
+
adjf = elements["ADJF"]
|
|
75
|
+
prdt = elements["PRDT"]
|
|
76
|
+
pvdt = elements["PVDT"]
|
|
77
|
+
prrd = elements["PRRD"]
|
|
78
|
+
cppu = elements["CPPU"]
|
|
79
|
+
ctot = elements["CTOT"]
|
|
80
|
+
csdt = elements["CSDT"]
|
|
81
|
+
cedt = elements["CEDT"]
|
|
82
|
+
mloc = elements["MLOC"]
|
|
83
|
+
|
|
84
|
+
ccde_supplier_code = ccde[2]
|
|
85
|
+
tpref, rate_divisor, units_divisor = SUPPLIER_CODE_MAP[ccde_supplier_code]
|
|
86
|
+
|
|
87
|
+
rate_str = cppu[0]
|
|
88
|
+
|
|
89
|
+
rate_key = f"{tpref}_rate"
|
|
90
|
+
if rate_key not in breakdown:
|
|
91
|
+
breakdown[rate_key] = set()
|
|
92
|
+
rate = Decimal(rate_str) / rate_divisor
|
|
93
|
+
breakdown[rate_key].add(rate)
|
|
94
|
+
|
|
95
|
+
breakdown[f"{tpref}_gbp"] += to_decimal(ctot) / Decimal("100")
|
|
96
|
+
|
|
97
|
+
suff = NUCT_LOOKUP[nuct[1].strip()]
|
|
98
|
+
key = f"{tpref}_{suff}"
|
|
99
|
+
units_billed = to_decimal(nuct) / units_divisor
|
|
100
|
+
breakdown[key] += units_billed
|
|
101
|
+
|
|
102
|
+
if "start_date" not in headers:
|
|
103
|
+
headers["start_date"] = to_date(csdt[0])
|
|
104
|
+
|
|
105
|
+
if "finish_date" not in headers:
|
|
106
|
+
headers["finish_date"] = _to_finish_date(cedt[0])
|
|
107
|
+
|
|
108
|
+
if "mprn" not in headers:
|
|
109
|
+
headers["mprn"] = mloc[0]
|
|
110
|
+
|
|
111
|
+
if len(conb) > 0 and len(conb[0]) > 0:
|
|
112
|
+
headers["breakdown"]["units_consumed"] += to_decimal(conb) / Decimal("1000")
|
|
113
|
+
|
|
114
|
+
if tpref == "commodity":
|
|
115
|
+
headers["kwh"] += units_billed
|
|
116
|
+
|
|
117
|
+
if len(prrd) > 0 and len(prrd[0]) > 0:
|
|
118
|
+
pres_read_date = to_date(prdt[0])
|
|
119
|
+
prev_read_date = to_date(pvdt[0])
|
|
120
|
+
|
|
121
|
+
pres_read_value = Decimal(prrd[0])
|
|
122
|
+
pres_read_type = READ_TYPE_MAP[prrd[1]]
|
|
123
|
+
prev_read_value = Decimal(prrd[2])
|
|
124
|
+
prev_read_type = READ_TYPE_MAP[prrd[3]]
|
|
125
|
+
msn = mtnr[0]
|
|
126
|
+
unit = UNIT_MAP[conb[1]]
|
|
127
|
+
correction_factor = Decimal(adjf[1]) / Decimal(100000)
|
|
128
|
+
|
|
129
|
+
headers["reads"].append(
|
|
130
|
+
{
|
|
131
|
+
"msn": msn,
|
|
132
|
+
"unit": unit,
|
|
133
|
+
"correction_factor": correction_factor,
|
|
134
|
+
"prev_date": prev_read_date,
|
|
135
|
+
"prev_value": prev_read_value,
|
|
136
|
+
"prev_type_code": prev_read_type,
|
|
137
|
+
"pres_date": pres_read_date,
|
|
138
|
+
"pres_value": pres_read_value,
|
|
139
|
+
"pres_type_code": pres_read_type,
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _process_MTR(elements, headers):
|
|
145
|
+
if headers["message_type"] == "UTLBIL":
|
|
146
|
+
breakdown = headers["breakdown"]
|
|
147
|
+
for k, v in tuple(breakdown.items()):
|
|
148
|
+
if isinstance(v, set):
|
|
149
|
+
breakdown[k] = sorted(v)
|
|
150
|
+
|
|
151
|
+
for read in headers["reads"]:
|
|
152
|
+
read["calorific_value"] = headers["cv"]
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"raw_lines": "\n".join(headers["raw_lines"]),
|
|
156
|
+
"mprn": headers["mprn"],
|
|
157
|
+
"reference": headers["reference"],
|
|
158
|
+
"account": headers["mprn"],
|
|
159
|
+
"reads": headers["reads"],
|
|
160
|
+
"kwh": headers["kwh"],
|
|
161
|
+
"breakdown": headers["breakdown"],
|
|
162
|
+
"net_gbp": headers["net"],
|
|
163
|
+
"vat_gbp": headers["vat"],
|
|
164
|
+
"gross_gbp": headers["gross"],
|
|
165
|
+
"bill_type_code": headers["bill_type_code"],
|
|
166
|
+
"start_date": headers["start_date"],
|
|
167
|
+
"finish_date": headers["finish_date"],
|
|
168
|
+
"issue_date": headers["issue_date"],
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _process_VAT(elements, headers):
|
|
173
|
+
breakdown = headers["breakdown"]
|
|
174
|
+
vatp = elements["VATP"]
|
|
175
|
+
if "vat" in breakdown:
|
|
176
|
+
vat = breakdown["vat"]
|
|
177
|
+
else:
|
|
178
|
+
vat = breakdown["vat"] = {}
|
|
179
|
+
|
|
180
|
+
vat_perc = to_decimal(vatp) / Decimal(1000)
|
|
181
|
+
try:
|
|
182
|
+
vat_bd = vat[vat_perc]
|
|
183
|
+
except KeyError:
|
|
184
|
+
vat_bd = vat[vat_perc] = defaultdict(int)
|
|
185
|
+
|
|
186
|
+
uvtt = elements["UVTT"]
|
|
187
|
+
vat_gbp = to_decimal(uvtt) / Decimal("100")
|
|
188
|
+
|
|
189
|
+
vat_bd["vat"] += vat_gbp
|
|
190
|
+
|
|
191
|
+
uvla = elements["UVLA"]
|
|
192
|
+
net_gbp = to_decimal(uvla) / Decimal("100")
|
|
193
|
+
vat_bd["net"] += net_gbp
|
|
194
|
+
headers["net"] += net_gbp
|
|
195
|
+
headers["vat"] += vat_gbp
|
|
196
|
+
ucsi = elements["UCSI"]
|
|
197
|
+
headers["gross"] += to_decimal(ucsi) / Decimal("100")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _process_NOOP(elements, headers):
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
CODE_FUNCS = {
|
|
205
|
+
"ADJ": _process_ADJ,
|
|
206
|
+
"BCD": _process_BCD,
|
|
207
|
+
"BTL": _process_NOOP,
|
|
208
|
+
"CCD1": _process_NOOP,
|
|
209
|
+
"CCD2": _process_CCD2,
|
|
210
|
+
"CCD3": _process_NOOP,
|
|
211
|
+
"CCD4": _process_NOOP,
|
|
212
|
+
"CDT": _process_NOOP,
|
|
213
|
+
"CLO": _process_NOOP,
|
|
214
|
+
"END": _process_NOOP,
|
|
215
|
+
"FIL": _process_NOOP,
|
|
216
|
+
"MHD": _process_MHD,
|
|
217
|
+
"MTR": _process_MTR,
|
|
218
|
+
"SDT": _process_NOOP,
|
|
219
|
+
"STX": _process_NOOP,
|
|
220
|
+
"TYP": _process_NOOP,
|
|
221
|
+
"TTL": _process_NOOP,
|
|
222
|
+
"VAT": _process_VAT,
|
|
223
|
+
"VTS": _process_NOOP,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class Parser:
|
|
228
|
+
def __init__(self, file_bytes):
|
|
229
|
+
self.edi_str = str(file_bytes, "utf-8", errors="ignore")
|
|
230
|
+
self.line_number = None
|
|
231
|
+
|
|
232
|
+
def make_raw_bills(self):
|
|
233
|
+
bills = []
|
|
234
|
+
headers = {}
|
|
235
|
+
bill = None
|
|
236
|
+
for self.line_number, line, seg_name, elements in parse_edi(self.edi_str):
|
|
237
|
+
try:
|
|
238
|
+
func = CODE_FUNCS[seg_name]
|
|
239
|
+
except KeyError:
|
|
240
|
+
raise BadRequest(f"Code {seg_name} not recognized.")
|
|
241
|
+
try:
|
|
242
|
+
bill = func(elements, headers)
|
|
243
|
+
except BaseException as e:
|
|
244
|
+
raise Exception(f"Propblem with segment {line}: {e}") from e
|
|
245
|
+
|
|
246
|
+
if "raw_lines" in headers:
|
|
247
|
+
headers["raw_lines"].append(line)
|
|
248
|
+
if bill is not None:
|
|
249
|
+
bills.append(bill)
|
|
250
|
+
|
|
251
|
+
return bills
|
chellow/gas/engine.py
CHANGED
|
@@ -502,13 +502,6 @@ class GDataSource:
|
|
|
502
502
|
* h["calorific_value"]
|
|
503
503
|
/ 3.6
|
|
504
504
|
)
|
|
505
|
-
h["kwh_avg"] = (
|
|
506
|
-
h["units_consumed"]
|
|
507
|
-
* h["unit_factor"]
|
|
508
|
-
* h["correction_factor"]
|
|
509
|
-
* h["avg_cv"]
|
|
510
|
-
/ 3.6
|
|
511
|
-
)
|
|
512
505
|
h["ug_rate"] = float(
|
|
513
506
|
g_rates(sess, self.caches, "ug", h["start_date"], True)[
|
|
514
507
|
"ug_gbp_per_kwh"
|
|
@@ -703,9 +696,7 @@ def find_cv(sess, caches, dt, g_ldz_code):
|
|
|
703
696
|
avg_cv = year_cache[ct.month]
|
|
704
697
|
except KeyError:
|
|
705
698
|
cv_list = [float(v["cv"]) for v in cvs.values()]
|
|
706
|
-
avg_cv = year_cache[ct.month] =
|
|
707
|
-
sum(cv_list) / len(cv_list), ndigits=1
|
|
708
|
-
)
|
|
699
|
+
avg_cv = year_cache[ct.month] = sum(cv_list) / len(cv_list)
|
|
709
700
|
return cv, avg_cv
|
|
710
701
|
|
|
711
702
|
|
chellow/gas/views.py
CHANGED
|
@@ -289,15 +289,37 @@ def supply_edit_get(g_supply_id):
|
|
|
289
289
|
)
|
|
290
290
|
|
|
291
291
|
|
|
292
|
+
@gas.route("/supplies/<int:g_supply_id>/edit", methods=["DELETE"])
|
|
293
|
+
def supply_edit_delete(g_supply_id):
|
|
294
|
+
g_supply = GSupply.get_by_id(g.sess, g_supply_id)
|
|
295
|
+
try:
|
|
296
|
+
g_supply.delete(g.sess)
|
|
297
|
+
g.sess.commit()
|
|
298
|
+
return hx_redirect("/supplies")
|
|
299
|
+
except BadRequest as e:
|
|
300
|
+
flash(e.description)
|
|
301
|
+
g_eras = g.sess.scalars(
|
|
302
|
+
select(GEra)
|
|
303
|
+
.where(GEra.g_supply == g_supply)
|
|
304
|
+
.order_by(GEra.start_date.desc())
|
|
305
|
+
)
|
|
306
|
+
g_exit_zones = g.sess.query(GExitZone).order_by(GExitZone.code).all()
|
|
307
|
+
return make_response(
|
|
308
|
+
render_template(
|
|
309
|
+
"supply_edit.html",
|
|
310
|
+
g_supply=g_supply,
|
|
311
|
+
g_eras=g_eras,
|
|
312
|
+
g_exit_zones=g_exit_zones,
|
|
313
|
+
),
|
|
314
|
+
400,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
292
318
|
@gas.route("/supplies/<int:g_supply_id>/edit", methods=["POST"])
|
|
293
319
|
def supply_edit_post(g_supply_id):
|
|
294
320
|
g_supply = GSupply.get_by_id(g.sess, g_supply_id)
|
|
295
321
|
try:
|
|
296
|
-
if "
|
|
297
|
-
g_supply.delete(g.sess)
|
|
298
|
-
g.sess.commit()
|
|
299
|
-
return chellow_redirect("/supplies", 303)
|
|
300
|
-
elif "insert_g_era" in request.values:
|
|
322
|
+
if "insert_g_era" in request.values:
|
|
301
323
|
start_date = req_date("start")
|
|
302
324
|
g_supply.insert_g_era_at(g.sess, start_date)
|
|
303
325
|
g.sess.commit()
|
|
@@ -418,6 +440,16 @@ def batch_get(g_batch_id):
|
|
|
418
440
|
vbd["vat"] += g_bill.vat
|
|
419
441
|
vbd["net"] += g_bill.net
|
|
420
442
|
|
|
443
|
+
if "vat" in bd:
|
|
444
|
+
for vat_percentage, vat_bd in bd["vat"].items():
|
|
445
|
+
try:
|
|
446
|
+
vbd = vat_breakdown[vat_percentage]
|
|
447
|
+
except KeyError:
|
|
448
|
+
vbd = vat_breakdown[vat_percentage] = defaultdict(int)
|
|
449
|
+
|
|
450
|
+
vbd["vat"] += vat_bd["vat"]
|
|
451
|
+
vbd["net"] += vat_bd["net"]
|
|
452
|
+
|
|
421
453
|
config_contract = Contract.get_non_core_by_name(g.sess, "configuration")
|
|
422
454
|
properties = config_contract.make_properties()
|
|
423
455
|
|
|
@@ -45,18 +45,21 @@
|
|
|
45
45
|
<th>Issue Date</th>
|
|
46
46
|
<th>Start Date</th>
|
|
47
47
|
<th>Finish Date</th>
|
|
48
|
+
<th>kWh</th>
|
|
48
49
|
<th>Net GBP</th>
|
|
49
|
-
<th>VAT 5%</th>
|
|
50
|
-
<th>VAT 15%</th>
|
|
51
|
-
<th>VAT 17.5%</th>
|
|
52
|
-
<th>VAT 20%</th>
|
|
53
50
|
<th>VAT</th>
|
|
54
51
|
<th>Gross GBP</th>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
<th>Breakdown</th>
|
|
53
|
+
<th>R1 calorific_value</th>
|
|
54
|
+
<th>R1 correction_factor</th>
|
|
55
|
+
<th>R1 msn</th>
|
|
56
|
+
<th>R1 unit</th>
|
|
57
|
+
<th>R1 prev_date</th>
|
|
58
|
+
<th>R1 prev_value</th>
|
|
59
|
+
<th>R1 prev_type_code</th>
|
|
60
|
+
<th>R1 pres_date</th>
|
|
61
|
+
<th>R1 pres_value</th>
|
|
62
|
+
<th>R1 pres_type_code</th>
|
|
60
63
|
</tr>
|
|
61
64
|
</thead>
|
|
62
65
|
<tbody>
|
|
@@ -70,17 +73,22 @@
|
|
|
70
73
|
<td>{{bill.issue_date|hh_format}}</td>
|
|
71
74
|
<td>{{bill.start_date|hh_format}}</td>
|
|
72
75
|
<td>{{bill.finish_date|hh_format}}</td>
|
|
76
|
+
<td>{{bill.kwh}}</td>
|
|
73
77
|
<td>{{bill.net_gbp}}</td>
|
|
74
|
-
<td>{{bill.vat_5pc}}</td>
|
|
75
|
-
<td>{{bill.vat_15pc}}</td>
|
|
76
|
-
<td>{{bill.vat_17_5pc}}</td>
|
|
77
|
-
<td>{{bill.vat_20pc}}</td>
|
|
78
78
|
<td>{{bill.vat_gbp}}</td>
|
|
79
79
|
<td>{{bill.gross_gbp}}</td>
|
|
80
|
+
<td>{{bill.breakdown|dumps}}</td>
|
|
80
81
|
{% for read in bill.reads %}
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
{
|
|
82
|
+
<td>{{read.calorific_value}}</td>
|
|
83
|
+
<td>{{read.correction_factor}}</td>
|
|
84
|
+
<td>{{read.msn}}</td>
|
|
85
|
+
<td>{{read. unit}}</td>
|
|
86
|
+
<td>{{read.prev_date|hh_format}}</td>
|
|
87
|
+
<td>{{read.prev_value}}</td>
|
|
88
|
+
<td>{{read.prev_type_code}}</td>
|
|
89
|
+
<td>{{read.pres_date|hh_format}}</td>
|
|
90
|
+
<td>{{read.pres_value}}</td>
|
|
91
|
+
<td>{{read.pres_type_code}}</td>
|
|
84
92
|
{% endfor %}
|
|
85
93
|
</tr>
|
|
86
94
|
{% endfor %}
|
|
@@ -10,67 +10,53 @@
|
|
|
10
10
|
{% endblock %}
|
|
11
11
|
|
|
12
12
|
{% block content %}
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
<form method="post" action="/g/supplies/{{g_supply.id}}/edit">
|
|
14
|
+
<fieldset>
|
|
15
|
+
<legend>Update this supply</legend>
|
|
16
|
+
<label>Name</label> {{input_text('name', g_supply.name)}}
|
|
17
|
+
<label>MPRN</label> {{input_text('mprn', g_supply.mprn)}}
|
|
18
|
+
<label>Exit Zone</label>
|
|
19
|
+
<select name="g_exit_zone_id">
|
|
20
|
+
{% for g_exit_zone in g_exit_zones %}
|
|
21
|
+
{{input_option(
|
|
22
|
+
'g_exit_zone_id', g_exit_zone.id, g_exit_zone.code, initial=g_supply.g_exit_zone.id)}}
|
|
23
|
+
{% endfor %}
|
|
24
|
+
</select>
|
|
25
|
+
<input type="submit" value="Update">
|
|
26
|
+
</fieldset>
|
|
27
|
+
</form>
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
<form hx-delete="/g/supplies/{{g_supply.id}}/edit"
|
|
30
|
+
hx-confirm="Are you sure you want to delete this supply?">
|
|
31
|
+
<fieldset>
|
|
32
|
+
<legend>Delete this supply</legend>
|
|
33
|
+
<input type="submit" value="Delete">
|
|
34
|
+
</fieldset>
|
|
35
|
+
</form>
|
|
25
36
|
|
|
26
|
-
|
|
37
|
+
<form method="post" action="/g/supplies/{{g_supply.id}}/edit">
|
|
38
|
+
<fieldset>
|
|
39
|
+
<legend>Insert a new era</legend>
|
|
40
|
+
<label>Start date</label> {{input_date('start', None)}}
|
|
41
|
+
<input type="submit" name="insert_g_era" value="Insert">
|
|
42
|
+
</fieldset>
|
|
43
|
+
</form>
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
</select>
|
|
39
|
-
<input type="submit" value="Update">
|
|
40
|
-
</fieldset>
|
|
41
|
-
</form>
|
|
42
|
-
<br>
|
|
43
|
-
<form action="/g/supplies/{{g_supply.id}}/edit">
|
|
44
|
-
<fieldset>
|
|
45
|
-
<legend>Delete this supply</legend>
|
|
46
|
-
<input type="submit" name="delete" value="Delete">
|
|
47
|
-
</fieldset>
|
|
48
|
-
</form>
|
|
49
|
-
<br>
|
|
50
|
-
<form method="post" action="/g/supplies/{{g_supply.id}}/edit">
|
|
51
|
-
<fieldset>
|
|
52
|
-
<legend>Insert a new era</legend>
|
|
53
|
-
<label>Start date</label> {{input_date('start', None)}}
|
|
54
|
-
<input type="submit" name="insert_g_era" value="Insert">
|
|
55
|
-
</fieldset>
|
|
56
|
-
</form>
|
|
57
|
-
<br>
|
|
58
|
-
<table>
|
|
59
|
-
<caption>Existing Eras</caption>
|
|
60
|
-
<thead>
|
|
45
|
+
<table>
|
|
46
|
+
<caption>Existing Eras</caption>
|
|
47
|
+
<thead>
|
|
48
|
+
<tr>
|
|
49
|
+
<th>Start date</th>
|
|
50
|
+
<th>Finish date</th>
|
|
51
|
+
</tr>
|
|
52
|
+
</thead>
|
|
53
|
+
<tbody>
|
|
54
|
+
{% for g_era in g_eras %}
|
|
61
55
|
<tr>
|
|
62
|
-
<
|
|
63
|
-
<
|
|
56
|
+
<td>{{g_era.start_date|hh_format}}</td>
|
|
57
|
+
<td>{{g_era.finish_date|hh_format}}</td>
|
|
64
58
|
</tr>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<tr>
|
|
69
|
-
<td>{{g_era.start_date|hh_format}}</td>
|
|
70
|
-
<td>{{g_era.finish_date|hh_format}}</td>
|
|
71
|
-
</tr>
|
|
72
|
-
{% endfor %}
|
|
73
|
-
</tbody>
|
|
74
|
-
</table>
|
|
75
|
-
{% endif %}
|
|
59
|
+
{% endfor %}
|
|
60
|
+
</tbody>
|
|
61
|
+
</table>
|
|
76
62
|
{% endblock %}
|
chellow/views.py
CHANGED
|
@@ -67,10 +67,10 @@ import chellow.e.mdd_importer
|
|
|
67
67
|
import chellow.e.rcrc
|
|
68
68
|
import chellow.e.system_price
|
|
69
69
|
import chellow.e.tlms
|
|
70
|
-
import chellow.edi_lib
|
|
71
70
|
import chellow.general_import
|
|
72
71
|
import chellow.national_grid
|
|
73
72
|
import chellow.rate_server
|
|
73
|
+
from chellow.edi_lib import SEGMENTS, parse_edi
|
|
74
74
|
from chellow.models import (
|
|
75
75
|
BillType,
|
|
76
76
|
Channel,
|
|
@@ -644,23 +644,19 @@ def edi_viewer_post():
|
|
|
644
644
|
else:
|
|
645
645
|
edi_str = req_str("edi_fragment")
|
|
646
646
|
|
|
647
|
-
|
|
648
|
-
f.seek(0)
|
|
649
|
-
parser = chellow.edi_lib.EdiParser(f)
|
|
650
|
-
for segment_name in parser:
|
|
651
|
-
elements = parser.elements
|
|
647
|
+
for line_number, line, segment_name, elements in parse_edi(edi_str):
|
|
652
648
|
|
|
653
649
|
if segment_name == "CCD":
|
|
654
|
-
segment_name = segment_name + elements[
|
|
650
|
+
segment_name = segment_name + elements["CCDE"][0]
|
|
655
651
|
try:
|
|
656
|
-
seg =
|
|
652
|
+
seg = SEGMENTS[segment_name]
|
|
657
653
|
except KeyError:
|
|
658
654
|
raise BadRequest(
|
|
659
655
|
f"The segment name {segment_name} is not recognized."
|
|
660
656
|
)
|
|
661
657
|
else:
|
|
662
658
|
try:
|
|
663
|
-
seg =
|
|
659
|
+
seg = SEGMENTS[segment_name]
|
|
664
660
|
except KeyError:
|
|
665
661
|
raise BadRequest(
|
|
666
662
|
f"The segment name {segment_name} is not recognized."
|
|
@@ -669,13 +665,14 @@ def edi_viewer_post():
|
|
|
669
665
|
titles_element = []
|
|
670
666
|
titles_component = []
|
|
671
667
|
values = []
|
|
672
|
-
elems = seg["elements"]
|
|
668
|
+
elems = {el["code"]: el for el in seg["elements"]}
|
|
673
669
|
if len(elements) > len(elems):
|
|
674
670
|
raise BadRequest(
|
|
675
671
|
f"There are more elements than recognized for the segment "
|
|
676
|
-
f"{segment_name}."
|
|
672
|
+
f"{segment_name}. {line}"
|
|
677
673
|
)
|
|
678
|
-
for
|
|
674
|
+
for element_code, element in elements.items():
|
|
675
|
+
elem = elems[element_code]
|
|
679
676
|
comps = elem["components"]
|
|
680
677
|
colspan = len(comps)
|
|
681
678
|
titles_element.append(
|
|
@@ -688,8 +685,9 @@ def edi_viewer_post():
|
|
|
688
685
|
)
|
|
689
686
|
if len(element) > len(comps):
|
|
690
687
|
raise BadRequest(
|
|
691
|
-
f"
|
|
692
|
-
f"{
|
|
688
|
+
f"For the segment {segment_name} the number of components "
|
|
689
|
+
f"{element} is greater than the expected components {comps}. "
|
|
690
|
+
f"{line}"
|
|
693
691
|
)
|
|
694
692
|
|
|
695
693
|
for i, (title, typ) in enumerate(comps):
|
|
@@ -726,7 +724,7 @@ def edi_viewer_post():
|
|
|
726
724
|
"titles_element": titles_element,
|
|
727
725
|
"titles_component": titles_component,
|
|
728
726
|
"vals": values,
|
|
729
|
-
"raw_line":
|
|
727
|
+
"raw_line": line,
|
|
730
728
|
}
|
|
731
729
|
)
|
|
732
730
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: chellow
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1716366171.0.0
|
|
4
4
|
Summary: Web Application for checking UK energy bills.
|
|
5
5
|
Project-URL: Homepage, https://github.com/WessexWater/chellow
|
|
6
6
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
@@ -3,7 +3,7 @@ chellow/api.py,sha256=mk17TfweR76DPFC8lX2SArTjai6y6YshASxqO1w-_-s,11036
|
|
|
3
3
|
chellow/bank_holidays.py,sha256=T_utYMwe_g1dz5X-aOTdIPryg49SvB7QsWM1yphlqG8,4423
|
|
4
4
|
chellow/commands.py,sha256=ESBe9ZWj1c3vdZgqMZ9gFvYAB3hRag2R1PzOwuw9yFo,1302
|
|
5
5
|
chellow/dloads.py,sha256=5SmP-0QPK6xCkd_wjIWh_8FAzN5OxNHCzyEQ1Xb-Y-M,5256
|
|
6
|
-
chellow/edi_lib.py,sha256=
|
|
6
|
+
chellow/edi_lib.py,sha256=alu20x9ZX06iPfnNI9dEJzuP6RIf4We3Y_M_bl7RrcY,51789
|
|
7
7
|
chellow/general_import.py,sha256=l3EHq9zG9Vfl5Ee6XTVrC1nusXo4YGGB4VBmZ_JaJR8,65798
|
|
8
8
|
chellow/models.py,sha256=2YSRKOt89mgawuz1CV02FsjLi5ue9Np38PGOIS-yoc4,242202
|
|
9
9
|
chellow/national_grid.py,sha256=uxcv0qisHPyzw9AVIYPzsRqwt2XPAcZL-SBR12qcrS0,4364
|
|
@@ -11,7 +11,7 @@ chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
|
|
|
11
11
|
chellow/rate_server.py,sha256=fg-Pf_9Hk3bXmC9riPQNGQxBvLvBa_WtNYdwDCjnCSg,5678
|
|
12
12
|
chellow/testing.py,sha256=Od4HHH6pZrhJ_De118_F55RJEKmAvhUH2S24QE9qFQk,3635
|
|
13
13
|
chellow/utils.py,sha256=32qPWEGzCPKPhSM7ztpzcR6ZG74sVZanScDIdK50Rq4,19308
|
|
14
|
-
chellow/views.py,sha256=
|
|
14
|
+
chellow/views.py,sha256=eDvTQM_PUqRvrCQvBdlF5q7MEs7w2yJmRjcC8WDbHtE,79584
|
|
15
15
|
chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
|
|
17
17
|
chellow/e/bill_importer.py,sha256=y1bpn49xDwltj0PRFowWsjAVamcD8eyxUbCTdGxEZiE,7389
|
|
@@ -58,13 +58,14 @@ chellow/e/bill_parsers/sse_edi.py,sha256=L85DOfNkqexeEIEr8pCBn_2sHJI-zEaw6cogpE3
|
|
|
58
58
|
chellow/e/bill_parsers/sww_xls.py,sha256=QEjiuvwvr5FuWCfqqVw8LaA_vZyAKsvRAS5fw3xtFhM,7533
|
|
59
59
|
chellow/gas/bill_import.py,sha256=w0lPgK_Drzh8rtnEBQe3qFuxrgzZ6qQSgpaGrrGznMU,6549
|
|
60
60
|
chellow/gas/bill_parser_csv.py,sha256=Ecdy-apFT-mWAxddAsM4k1s-9-FpIaOfjP0oFc0rdQg,5557
|
|
61
|
-
chellow/gas/bill_parser_engie_edi.py,sha256=
|
|
61
|
+
chellow/gas/bill_parser_engie_edi.py,sha256=Ko0vZP-QdVQ1uuhS_5cdrii60_cM4b_LFJMoY0pZqnk,8950
|
|
62
|
+
chellow/gas/bill_parser_total_edi.py,sha256=k5b9vadgLdEb8dk1E-mZpcKGyxXh1AWYsNiGSUK5CBA,7172
|
|
62
63
|
chellow/gas/ccl.py,sha256=DMlcPUELZi00CaDekVJINYk3GgH5apFrImVdmkbyba0,2913
|
|
63
64
|
chellow/gas/cv.py,sha256=4cdYYQ8Qak6NeYdBCB4YaQ0jX8-UkaydIIdibCQuXxM,7344
|
|
64
65
|
chellow/gas/dn_rate_parser.py,sha256=Mq8rAcUEUxIQOks59bsCKl8GrefvoHbrTCHqon9N0z0,11340
|
|
65
|
-
chellow/gas/engine.py,sha256=
|
|
66
|
+
chellow/gas/engine.py,sha256=jT7m6vddi5GnWd51wCYEVhBS-LZEn1T9ggZX7Y9HGK0,25263
|
|
66
67
|
chellow/gas/transportation.py,sha256=Bkg8TWOs-v0ES-4qqwbleiOhqbE_t2KauUx9JYMZELM,5300
|
|
67
|
-
chellow/gas/views.py,sha256=
|
|
68
|
+
chellow/gas/views.py,sha256=ZIMqByN0uBpf3y9wkMi-VogC1-NaHs-oO7WRVDk9Kkw,57428
|
|
68
69
|
chellow/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
70
|
chellow/reports/report_109.py,sha256=S8fsOwh-jVWGL0RsgyYLdqn00BAvlkBbi4VfTSGI-Mc,11181
|
|
70
71
|
chellow/reports/report_111.py,sha256=qU5MYPQzZ_cEvuERE8bxq93D-dN1yd4oML6FIqd0Jdk,28278
|
|
@@ -326,7 +327,7 @@ chellow/templates/g/batches.html,sha256=mnzLroYfhwvL5gFK1PNtI-vS7GcDtcggNd0E15Sh
|
|
|
326
327
|
chellow/templates/g/bill.html,sha256=S8moZ06CDl4_nQQgqyy4mdkyhfvgoQJGZS8ppsluT_E,3455
|
|
327
328
|
chellow/templates/g/bill_add.html,sha256=sDSpUgEbdalDsea1Ma5lgVRgtbFf0bZ042jUdOFeDDk,1674
|
|
328
329
|
chellow/templates/g/bill_edit.html,sha256=ynfUR_lZXLgTK3T0x9GjzAHahuR823ykMpjCWrY8ot8,2754
|
|
329
|
-
chellow/templates/g/bill_import.html,sha256=
|
|
330
|
+
chellow/templates/g/bill_import.html,sha256=gns_IbE7eynbbchydzLtuJ3SgJU2VXlr5211aqiRhe8,4630
|
|
330
331
|
chellow/templates/g/bill_imports.html,sha256=AHC0l0Wkr1RZ9fdGWTqihOEcn8lTZ63Uh9BHqPxfRCU,3157
|
|
331
332
|
chellow/templates/g/dn.html,sha256=ttEdvFANFUCBV8e9tVrZy35-tzsC9dU-biZhAPxE2Bw,481
|
|
332
333
|
chellow/templates/g/dns.html,sha256=RuxXvQ9eHs6B7nVGHtTbW8pdmSAaMbQw2f_BwiLZptM,403
|
|
@@ -357,12 +358,12 @@ chellow/templates/g/supplier_rate_script_add.html,sha256=zMjTkjupOsHFu-peUXL4IKj
|
|
|
357
358
|
chellow/templates/g/supplier_rate_script_edit.html,sha256=GoUqXf52gWEH1iApmNdxOcKLItzpA-o7iQPQ1_in9BA,1950
|
|
358
359
|
chellow/templates/g/supplies.html,sha256=oEAEfZaAuKv7EA6fd3blWjPwv_XKoNHLPlkRJ_afZHs,1267
|
|
359
360
|
chellow/templates/g/supply.html,sha256=Jeqm7AhZQd4uzu-ncRIIeh6DK9NYSvYrLrxmBE4ERIM,10975
|
|
360
|
-
chellow/templates/g/supply_edit.html,sha256=
|
|
361
|
+
chellow/templates/g/supply_edit.html,sha256=F2Ip4xCXLl4eFiRjMLZyUMIiznWw97GF7mN_50QAEWA,1653
|
|
361
362
|
chellow/templates/g/supply_note_add.html,sha256=npmobg9u1xKcgJKSRCwLY_tH5WJ0bAx3i2XZWqfFeHs,744
|
|
362
363
|
chellow/templates/g/supply_note_edit.html,sha256=6UQf_qbhFDys3cVsTp-c7ABWZpggW9R1rhxRKK3h_p8,1043
|
|
363
364
|
chellow/templates/g/supply_notes.html,sha256=WR3YwGh_qqTklSJ7JqWX6BKBc9rk_jMff4RiWZiF2CM,936
|
|
364
365
|
chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
|
|
365
366
|
chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
|
|
366
|
-
chellow-
|
|
367
|
-
chellow-
|
|
368
|
-
chellow-
|
|
367
|
+
chellow-1716366171.0.0.dist-info/METADATA,sha256=5ILGiNhUzVRJ-d34v1fsQjuGDkLxbpVzg-DM_uxNmwY,12205
|
|
368
|
+
chellow-1716366171.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
369
|
+
chellow-1716366171.0.0.dist-info/RECORD,,
|
|
File without changes
|