chellow 1715858374.0.0__py3-none-any.whl → 1715955542.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 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
- self.f_iterator = iter(f)
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.f_iterator).strip()
29
+ self.line = next(self.line_iterator).strip()
19
30
  self.line_number += 1
20
31
 
21
- if self.line[-1] != "'":
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.splitlines(), start=1):
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:-1].split("+")]
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
- return to_utc(to_ct_date(component))
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):
@@ -0,0 +1,403 @@
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
+ TCOD_MAP = {
16
+ "Energy Bill Discount Scheme": {"PPK": "ebrs"},
17
+ "Energy Bill Relief Scheme": {"PPK": "ebrs"},
18
+ "Energy Bill Relief Scheme Discount": {"PPK": "ebrs"},
19
+ "Unidentified Gas": {"PPK": "ug"},
20
+ "Commodity": {"PPK": "commodity"},
21
+ "Transportation": {"PPD": "transportation_fixed", "PPK": "transportation_variable"},
22
+ "Gas Flexi": {"PPK": "commodity"},
23
+ "Flex - Gas Flexi (New)": {"PPK": "commodity"},
24
+ "Meter Reading": {"PPD": "meter_read"},
25
+ "Meter Reading Credit Oct 19": {"FIX": "meter_read"},
26
+ "Meter Rental": {"PPD": "metering"},
27
+ "CCL": {"PPK": "ccl"},
28
+ "Consumption Based Administration": {"PPK": "admin_variable"},
29
+ "Swing": {"PPK": "swing"},
30
+ }
31
+
32
+ SUPPLIER_CODE_MAP = {
33
+ "STD": "standing",
34
+ "MET": "commodity",
35
+ "CCL": "ccl",
36
+ }
37
+
38
+ UNIT_MAP = {"M3": "M3", "HH": "HCUF", "HCUF": "HCUF"}
39
+
40
+
41
+ def _to_finish_date(date_str):
42
+ if len(date_str) == 0:
43
+ return None
44
+ return to_utc(
45
+ to_ct(Datetime.strptime(date_str, "%y%m%d") + relativedelta(days=1) - HH)
46
+ )
47
+
48
+
49
+ def _process_ADJ(elements, headers):
50
+ adjf = elements["ADJF"]
51
+ if adjf[0] == "CV":
52
+ headers["cv"] = Decimal(adjf[1]) / Decimal(100000)
53
+
54
+
55
+ def _process_BCD(elements, headers):
56
+ ivdt = elements["IVDT"]
57
+ headers["issue_date"] = to_date(ivdt[0])
58
+
59
+ invn = elements["INVN"]
60
+ headers["reference"] = invn[0]
61
+
62
+ btcd = elements["BTCD"]
63
+ headers["bill_type_code"] = btcd[0]
64
+
65
+ sumo = elements["SUMO"]
66
+ start_date = to_date(sumo[0])
67
+ if start_date is not None:
68
+ headers["start_date"] = start_date
69
+
70
+ if len(sumo) > 1:
71
+ finish_date = _to_finish_date(sumo[1])
72
+ if finish_date is not None:
73
+ headers["finish_date"] = finish_date
74
+
75
+
76
+ def _process_MHD(elements, headers):
77
+ headers.clear()
78
+
79
+ typ = elements["TYPE"]
80
+ message_type = headers["message_type"] = typ[0]
81
+ if message_type == "UTLBIL":
82
+ headers["reads"] = []
83
+ headers["raw_lines"] = []
84
+ headers["breakdown"] = defaultdict(int, {"units_consumed": Decimal(0)})
85
+ headers["kwh"] = Decimal("0.00")
86
+ headers["net"] = Decimal("0.00")
87
+ headers["vat"] = Decimal("0.00")
88
+ headers["gross"] = Decimal("0.00")
89
+
90
+
91
+ def _process_CCD1(elements, headers):
92
+ mtnr = elements["MTNR"]
93
+ msn = mtnr[0]
94
+
95
+ mloc = elements["MLOC"]
96
+
97
+ # Bug in EDI where MPRN missing in second CCD 1
98
+ if "mprn" not in headers:
99
+ headers["mprn"] = mloc[0]
100
+
101
+ prdt = elements["PRDT"]
102
+ pvdt = elements["PVDT"]
103
+
104
+ pres_read_date = to_date(prdt[0])
105
+ prev_read_date = to_date(pvdt[0])
106
+
107
+ prrd = elements["PRRD"]
108
+ pres_read_value = Decimal(prrd[0])
109
+ pres_read_type = READ_TYPE_MAP[prrd[1]]
110
+ prev_read_value = Decimal(prrd[2])
111
+ prev_read_type = READ_TYPE_MAP[prrd[3]]
112
+
113
+ conb = elements["CONB"]
114
+ unit = UNIT_MAP[conb[1]]
115
+ headers["breakdown"]["units_consumed"] += to_decimal(conb) / Decimal("1000")
116
+
117
+ adjf = elements["ADJF"]
118
+ correction_factor = Decimal(adjf[1]) / Decimal(100000)
119
+
120
+ nuct = elements["NUCT"]
121
+
122
+ headers["kwh"] += to_decimal(nuct) / Decimal("1000")
123
+
124
+ headers["reads"].append(
125
+ {
126
+ "msn": msn,
127
+ "unit": unit,
128
+ "correction_factor": correction_factor,
129
+ "prev_date": prev_read_date,
130
+ "prev_value": prev_read_value,
131
+ "prev_type_code": prev_read_type,
132
+ "pres_date": pres_read_date,
133
+ "pres_value": pres_read_value,
134
+ "pres_type_code": pres_read_type,
135
+ }
136
+ )
137
+
138
+
139
+ NUCT_LOOKUP = {"DAY": "days", "KWH": "kwh"}
140
+
141
+
142
+ def _process_CCD2(elements, headers):
143
+ breakdown = headers["breakdown"]
144
+ ccde = elements["CCDE"]
145
+ ccde_supplier_code = ccde[2]
146
+ tcod = elements["TCOD"]
147
+ nuct = elements["NUCT"]
148
+ mtnr = elements["MTNR"]
149
+ conb = elements["CONB"]
150
+ adjf = elements["ADJF"]
151
+ prdt = elements["PRDT"]
152
+ pvdt = elements["PVDT"]
153
+ prrd = elements["PRRD"]
154
+
155
+ if len(tcod) > 1:
156
+ tpref_lookup = TCOD_MAP[tcod[1]]
157
+ else:
158
+ tpref_lookup = SUPPLIER_CODE_MAP
159
+
160
+ tpref = tpref_lookup[ccde_supplier_code]
161
+
162
+ bpri = elements["BPRI"]
163
+ if len(bpri[0]) > 0:
164
+ rate_key = f"{tpref}_rate"
165
+ if rate_key not in breakdown:
166
+ breakdown[rate_key] = set()
167
+ rate = Decimal(bpri[0]) / Decimal("10000000")
168
+ breakdown[rate_key].add(rate)
169
+
170
+ try:
171
+ ctot = elements["CTOT"]
172
+ breakdown[f"{tpref}_gbp"] += to_decimal(ctot) / Decimal("100")
173
+
174
+ if len(nuct) > 1:
175
+ key = NUCT_LOOKUP[nuct[1]]
176
+ else:
177
+ if ccde_supplier_code == "PPK":
178
+ key = f"{tpref}_kwh"
179
+ elif ccde_supplier_code == "PPD":
180
+ key = f"{tpref}_days"
181
+
182
+ breakdown[key] += to_decimal(nuct) / Decimal("1000")
183
+ except KeyError:
184
+ pass
185
+
186
+ if "start_date" not in headers:
187
+ csdt = elements["CSDT"]
188
+ start_date = to_date(csdt[0])
189
+ if start_date is not None:
190
+ headers["start_date"] = start_date
191
+
192
+ if "finish_date" not in headers:
193
+ cedt = elements["CSDT"]
194
+ finish_date = _to_finish_date(cedt[0])
195
+ if finish_date is not None:
196
+ headers["finish_date"] = finish_date
197
+
198
+ if "mprn" not in headers:
199
+ mloc = elements["MLOC"]
200
+ headers["mprn"] = mloc[0]
201
+
202
+ if len(conb) > 0 and len(conb[0]) > 0:
203
+ headers["breakdown"]["units_consumed"] += to_decimal(conb) / Decimal("1000")
204
+
205
+ if len(prrd) > 0 and len(prrd[0]) > 0:
206
+ pres_read_date = to_date(prdt[0])
207
+ prev_read_date = to_date(pvdt[0])
208
+
209
+ pres_read_value = Decimal(prrd[0])
210
+ pres_read_type = READ_TYPE_MAP[prrd[1]]
211
+ prev_read_value = Decimal(prrd[2])
212
+ prev_read_type = READ_TYPE_MAP[prrd[3]]
213
+ msn = mtnr[0]
214
+ unit = UNIT_MAP[conb[1]]
215
+ correction_factor = Decimal(adjf[1]) / Decimal(100000)
216
+
217
+ headers["reads"].append(
218
+ {
219
+ "msn": msn,
220
+ "unit": unit,
221
+ "correction_factor": correction_factor,
222
+ "prev_date": prev_read_date,
223
+ "prev_value": prev_read_value,
224
+ "prev_type_code": prev_read_type,
225
+ "pres_date": pres_read_date,
226
+ "pres_value": pres_read_value,
227
+ "pres_type_code": pres_read_type,
228
+ }
229
+ )
230
+
231
+
232
+ def _process_CCD3(elements, headers):
233
+ breakdown = headers["breakdown"]
234
+ ccde = elements["CCDE"]
235
+ ccde_supplier_code = ccde[2]
236
+ tcod = elements["TCOD"]
237
+
238
+ tpref = TCOD_MAP[tcod[1]][ccde_supplier_code]
239
+
240
+ bpri = elements["BPRI"]
241
+ bpri_str = bpri[0]
242
+ if len(bpri_str) > 0:
243
+ rate_key = f"{tpref}_rate"
244
+ if rate_key not in breakdown:
245
+ breakdown[rate_key] = set()
246
+ rate = Decimal(bpri_str) / Decimal("10000000")
247
+ breakdown[rate_key].add(rate)
248
+
249
+ nuct = elements["NUCT"]
250
+
251
+ try:
252
+ ctot = elements["CTOT"]
253
+ breakdown[f"{tpref}_gbp"] += to_decimal(ctot) / Decimal("100")
254
+
255
+ if ccde_supplier_code == "PPK":
256
+ key = f"{tpref}_kwh"
257
+ elif ccde_supplier_code == "PPD":
258
+ key = f"{tpref}_days"
259
+
260
+ breakdown[key] += to_decimal(nuct) / Decimal("1000")
261
+ except KeyError:
262
+ pass
263
+
264
+
265
+ def _process_CCD4(elements, headers):
266
+ breakdown = headers["breakdown"]
267
+ ccde = elements["ccde"]
268
+ ccde_supplier_code = ccde[2]
269
+ tcod = elements["TCOD"]
270
+
271
+ tpref = TCOD_MAP[tcod[1]][ccde_supplier_code]
272
+
273
+ bpri = elements["BPRI"]
274
+ rate_key = f"{tpref}_rate"
275
+ if rate_key not in breakdown:
276
+ breakdown[rate_key] = set()
277
+ rate = Decimal(bpri[0]) / Decimal("10000000")
278
+ breakdown[rate_key].add(rate)
279
+
280
+ nuct = elements["NUCT"]
281
+
282
+ try:
283
+ ctot = elements["CTOT"]
284
+ breakdown[tpref + "_gbp"] += to_decimal(ctot) / Decimal("100")
285
+
286
+ if ccde_supplier_code == "PPK":
287
+ key = f"{tpref}_kwh"
288
+ elif ccde_supplier_code == "PPD":
289
+ key = f"{tpref}_days"
290
+
291
+ breakdown[key] += to_decimal(nuct) / Decimal("1000")
292
+ except KeyError:
293
+ pass
294
+
295
+
296
+ def _process_MTR(elements, headers):
297
+ if headers["message_type"] == "UTLBIL":
298
+ breakdown = headers["breakdown"]
299
+ for k, v in tuple(breakdown.items()):
300
+ if isinstance(v, set):
301
+ breakdown[k] = sorted(v)
302
+
303
+ for read in headers["reads"]:
304
+ read["calorific_value"] = headers["cv"]
305
+
306
+ return {
307
+ "raw_lines": "\n".join(headers["raw_lines"]),
308
+ "mprn": headers["mprn"],
309
+ "reference": headers["reference"],
310
+ "account": headers["mprn"],
311
+ "reads": headers["reads"],
312
+ "kwh": headers["kwh"],
313
+ "breakdown": headers["breakdown"],
314
+ "net_gbp": headers["net"],
315
+ "vat_gbp": headers["vat"],
316
+ "gross_gbp": headers["gross"],
317
+ "bill_type_code": headers["bill_type_code"],
318
+ "start_date": headers["start_date"],
319
+ "finish_date": headers["finish_date"],
320
+ "issue_date": headers["issue_date"],
321
+ }
322
+
323
+
324
+ def _process_VAT(elements, headers):
325
+ breakdown = headers["breakdown"]
326
+ vatp = elements["VATP"]
327
+ if "vat" in breakdown:
328
+ vat = breakdown["vat"]
329
+ else:
330
+ vat = breakdown["vat"] = {}
331
+
332
+ vat_perc = to_decimal(vatp) / Decimal(1000)
333
+ try:
334
+ vat_bd = vat[vat_perc]
335
+ except KeyError:
336
+ vat_bd = vat[vat_perc] = defaultdict(int)
337
+
338
+ uvtt = elements["UVTT"]
339
+ vat_gbp = to_decimal(uvtt) / Decimal("100")
340
+
341
+ vat_bd["vat"] += vat_gbp
342
+
343
+ uvla = elements["UVLA"]
344
+ net_gbp = to_decimal(uvla) / Decimal("100")
345
+ vat_bd["net"] += net_gbp
346
+ headers["net"] += net_gbp
347
+ headers["vat"] += vat_gbp
348
+ ucsi = elements["UCSI"]
349
+ headers["gross"] += to_decimal(ucsi) / Decimal("100")
350
+
351
+
352
+ def _process_NOOP(elements, headers):
353
+ pass
354
+
355
+
356
+ CODE_FUNCS = {
357
+ "ADJ": _process_ADJ,
358
+ "BCD": _process_BCD,
359
+ "BTL": _process_NOOP,
360
+ "CCD1": _process_CCD1,
361
+ "CCD2": _process_CCD2,
362
+ "CCD3": _process_CCD3,
363
+ "CCD4": _process_CCD4,
364
+ "CDT": _process_NOOP,
365
+ "CLO": _process_NOOP,
366
+ "END": _process_NOOP,
367
+ "FIL": _process_NOOP,
368
+ "MHD": _process_MHD,
369
+ "MTR": _process_MTR,
370
+ "SDT": _process_NOOP,
371
+ "STX": _process_NOOP,
372
+ "TYP": _process_NOOP,
373
+ "TTL": _process_NOOP,
374
+ "VAT": _process_VAT,
375
+ "VTS": _process_NOOP,
376
+ }
377
+
378
+
379
+ class Parser:
380
+ def __init__(self, file_bytes):
381
+ self.edi_str = str(file_bytes, "utf-8", errors="ignore")
382
+ self.line_number = None
383
+
384
+ def make_raw_bills(self):
385
+ bills = []
386
+ headers = {}
387
+ bill = None
388
+ for self.line_number, line, seg_name, elements in parse_edi(self.edi_str):
389
+ try:
390
+ func = CODE_FUNCS[seg_name]
391
+ except KeyError:
392
+ raise BadRequest(f"Code {seg_name} not recognized.")
393
+ try:
394
+ bill = func(elements, headers)
395
+ except BaseException as e:
396
+ raise Exception(f"Propblem with segment {line}: {e}") from e
397
+
398
+ if "raw_lines" in headers:
399
+ headers["raw_lines"].append(line)
400
+ if bill is not None:
401
+ bills.append(bill)
402
+
403
+ return bills
@@ -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
- bill = func(elements, headers)
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)
chellow/gas/views.py CHANGED
@@ -418,6 +418,16 @@ def batch_get(g_batch_id):
418
418
  vbd["vat"] += g_bill.vat
419
419
  vbd["net"] += g_bill.net
420
420
 
421
+ if "vat" in bd:
422
+ for vat_percentage, vat_bd in bd["vat"].items():
423
+ try:
424
+ vbd = vat_breakdown[vat_percentage]
425
+ except KeyError:
426
+ vbd = vat_breakdown[vat_percentage] = defaultdict(int)
427
+
428
+ vbd["vat"] += vat_bd["vat"]
429
+ vbd["net"] += vat_bd["net"]
430
+
421
431
  config_contract = Contract.get_non_core_by_name(g.sess, "configuration")
422
432
  properties = config_contract.make_properties()
423
433
 
@@ -7,7 +7,7 @@ from dateutil.relativedelta import relativedelta
7
7
 
8
8
  from flask import g
9
9
 
10
- from sqlalchemy.sql.expression import true
10
+ from sqlalchemy.sql.expression import false, select, true
11
11
 
12
12
  from chellow.dloads import open_file
13
13
  from chellow.models import (
@@ -21,11 +21,11 @@ from chellow.models import (
21
21
  Supply,
22
22
  User,
23
23
  )
24
- from chellow.utils import csv_make_val, hh_before, req_int, utc_datetime_now
24
+ from chellow.utils import csv_make_val, hh_before, req_bool, req_int, utc_datetime_now
25
25
  from chellow.views import chellow_redirect
26
26
 
27
27
 
28
- def content(contract_id, days_hidden, user_id):
28
+ def content(contract_id, days_hidden, is_ignored, user_id):
29
29
  f = writer = None
30
30
  try:
31
31
  with Session() as sess:
@@ -54,15 +54,14 @@ def content(contract_id, days_hidden, user_id):
54
54
 
55
55
  now = utc_datetime_now()
56
56
  cutoff_date = now - relativedelta(days=days_hidden)
57
-
58
- for snag, channel, era, supply, site_era, site in (
59
- sess.query(Snag, Channel, Era, Supply, SiteEra, Site)
57
+ q = (
58
+ select(Snag, Channel, Era, Supply, SiteEra, Site)
60
59
  .join(Channel, Snag.channel_id == Channel.id)
61
60
  .join(Era, Channel.era_id == Era.id)
62
61
  .join(Supply, Era.supply_id == Supply.id)
63
62
  .join(SiteEra, Era.site_eras)
64
63
  .join(Site, SiteEra.site_id == Site.id)
65
- .filter(
64
+ .where(
66
65
  SiteEra.is_physical == true(),
67
66
  Era.dc_contract == contract,
68
67
  Snag.start_date < cutoff_date,
@@ -76,7 +75,11 @@ def content(contract_id, days_hidden, user_id):
76
75
  Snag.start_date,
77
76
  Snag.id,
78
77
  )
79
- ):
78
+ )
79
+ if not is_ignored:
80
+ q = q.where(Snag.is_ignored == false())
81
+
82
+ for snag, channel, era, supply, site_era, site in sess.execute(q):
80
83
  snag_start = snag.start_date
81
84
  snag_finish = snag.finish_date
82
85
  imp_mc = "" if era.imp_mpan_core is None else era.imp_mpan_core
@@ -123,7 +126,8 @@ def content(contract_id, days_hidden, user_id):
123
126
  def do_get(sess):
124
127
  contract_id = req_int("dc_contract_id")
125
128
  days_hidden = req_int("days_hidden")
129
+ is_ignored = req_bool("is_ignored")
126
130
 
127
- args = contract_id, days_hidden, g.user.id
131
+ args = contract_id, days_hidden, is_ignored, g.user.id
128
132
  threading.Thread(target=content, args=args).start()
129
133
  return chellow_redirect("/downloads", 303)
@@ -17,7 +17,8 @@
17
17
  <legend>Download CSV</legend>
18
18
  <input type="hidden" name="dc_contract_id" value="{{contract.id}}">
19
19
  <label>Hide snags &lt; days old</label>
20
- <input size="3" maxlength="3" name="days_hidden" value="0">
20
+ {{input_text('days_hidden', '0', 3, 3)}}
21
+ <label>Include ignored snags</label> {{input_checkbox('is_ignored', False)}}
21
22
  <input type="submit" value="Download">
22
23
  </fieldset>
23
24
  </form>
@@ -46,17 +46,19 @@
46
46
  <th>Start Date</th>
47
47
  <th>Finish Date</th>
48
48
  <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
49
  <th>VAT</th>
54
50
  <th>Gross GBP</th>
55
- {% for read in failed_bills[0]['reads'] %}
56
- {% for k in read|sort %}
57
- <th>{{k}}</th>
58
- {% endfor %}
59
- {% endfor %}
51
+ <th>Breakdown</th>
52
+ <th>R1 calorific_value</th>
53
+ <th>R1 correction_factor</th>
54
+ <th>R1 msn</th>
55
+ <th>R1 unit</th>
56
+ <th>R1 prev_date</th>
57
+ <th>R1 prev_value</th>
58
+ <th>R1 prev_type_code</th>
59
+ <th>R1 pres_date</th>
60
+ <th>R1 pres_value</th>
61
+ <th>R1 pres_type_code</th>
60
62
  </tr>
61
63
  </thead>
62
64
  <tbody>
@@ -71,16 +73,20 @@
71
73
  <td>{{bill.start_date|hh_format}}</td>
72
74
  <td>{{bill.finish_date|hh_format}}</td>
73
75
  <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
76
  <td>{{bill.vat_gbp}}</td>
79
77
  <td>{{bill.gross_gbp}}</td>
78
+ <td>{{bill.breakdown|dumps}}</td>
80
79
  {% for read in bill.reads %}
81
- {% for k, v in read|dictsort %}
82
- <td>{{v}}</td>
83
- {% endfor %}
80
+ <td>{{read.calorific_value}}</td>
81
+ <td>{{read.correction_factor}}</td>
82
+ <td>{{read.msn}}</td>
83
+ <td>{{read. unit}}</td>
84
+ <td>{{read.prev_date|hh_format}}</td>
85
+ <td>{{read.prev_value}}</td>
86
+ <td>{{read.prev_type_code}}</td>
87
+ <td>{{read.pres_date|hh_format}}</td>
88
+ <td>{{read.pres_value}}</td>
89
+ <td>{{read.pres_type_code}}</td>
84
90
  {% endfor %}
85
91
  </tr>
86
92
  {% endfor %}
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
- f = StringIO(edi_str)
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[1][0]
650
+ segment_name = segment_name + elements["CCDE"][0]
655
651
  try:
656
- seg = chellow.edi_lib.SEGMENTS[segment_name]
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 = chellow.edi_lib.SEGMENTS[segment_name]
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 element, elem in zip(elements, elems):
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"There are more components than recognized for the segment "
692
- f"{segment_name} and element {element}."
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": parser.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: 1715858374.0.0
3
+ Version: 1715955542.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=het3R0DBGtXEo-Sy1zvXQuX9EWQ1X6Y33G4l1hYYuds,51859
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=L_mlM8Xi6u850SXMD2XgWdQmZVRB0QAkSdIN_BIHi-M,79550
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=2nlroUfNS4DV7fMLiTS_0MZanyFdmRn-4guR3dSK-Vg,8813
61
+ chellow/gas/bill_parser_edi.py,sha256=3PGofkIxfxWG1x5WM7pYFi0tfTmvOuusRxEc-76Zvi0,11484
62
+ chellow/gas/bill_parser_engie_edi.py,sha256=Ko0vZP-QdVQ1uuhS_5cdrii60_cM4b_LFJMoY0pZqnk,8950
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
66
  chellow/gas/engine.py,sha256=PhaCWDslhEzDmuCu5PMt3IpFANm27OO1bupq3yQCmc0,25518
66
67
  chellow/gas/transportation.py,sha256=Bkg8TWOs-v0ES-4qqwbleiOhqbE_t2KauUx9JYMZELM,5300
67
- chellow/gas/views.py,sha256=YFP8w0Y1PeH6tb2pPm4JTkUTmQw1AuYeO4pFXcf-1bY,56389
68
+ chellow/gas/views.py,sha256=0oiQBmhI6egENXdi0CcgjM80BaF0nxptPJvRKJdce_M,56781
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
@@ -74,7 +75,7 @@ chellow/reports/report_183.py,sha256=DZX-hHJPl_Tbgkt31C_cuLZg_5L2b6iCPQ5foOZizR0
74
75
  chellow/reports/report_187.py,sha256=UvpaYHjyJFNV5puYq8_KxfzoBtVrwFgIGUOmC5oGA9A,9956
75
76
  chellow/reports/report_219.py,sha256=o2eEg3mX9_raP2b4gjc2Gu4vqnMqcvvJBYQ1oQjxvpE,6637
76
77
  chellow/reports/report_231.py,sha256=gOb1AkXZQvwVpRg5cIenO7iR7Se1_zsWnJp9l2BlgpA,5008
77
- chellow/reports/report_233.py,sha256=DLAmkoHFKH-N8OEIoX4jeCxAaCfJK0e7ssjFJJpPqzw,4334
78
+ chellow/reports/report_233.py,sha256=cIatj-HHYW_GNIRsji-DlsmYjt8rUdm_5xujPLOYL8U,4537
78
79
  chellow/reports/report_241.py,sha256=AlFmSHnfG2HWv_ICmWX7fNpPwLHjq7mo1QtOTjSKO3k,5384
79
80
  chellow/reports/report_247.py,sha256=ozgCcee8XeqYbOpZCyU2STJKaz6h2x7TYQogagTaYLw,46626
80
81
  chellow/reports/report_29.py,sha256=KDFjgrLBv4WbG9efCdu_geMR7gT_QV9N97Wfdt7aDc4,2736
@@ -158,7 +159,7 @@ chellow/templates/e/channel_add.html,sha256=X1hi0b-ZjchlQ2m7uZYCyqkqU-Qd1s3_5u4z
158
159
  chellow/templates/e/channel_edit.html,sha256=OUkdiS2NBQ_w4gmxRxXAcRToM5BT8DWWJrtQMFs-GUU,2198
159
160
  chellow/templates/e/channel_snag.html,sha256=wBJ5KBfeJdAeRmaB0qu0AD9Z4nM5fn6tJ_8bNqTWtoo,1477
160
161
  chellow/templates/e/channel_snag_edit.html,sha256=sUFI4Ml3F1B35x8_t_Pz3hWuQN9Xtxr3kt4u8hdxNwY,1911
161
- chellow/templates/e/channel_snags.html,sha256=57bVmu2SwetdpWQbImMykO-qgY7wXi6Y9plRqykOGy4,2901
162
+ chellow/templates/e/channel_snags.html,sha256=tDZWp8s0gt5AtqArpclSl6hOafQCetz7Sp4MfZJ7dWQ,2964
162
163
  chellow/templates/e/comm.html,sha256=DSlAaDg1r4KYq9VUgDtt2lgW6apZjZvwhMujIJINmps,383
163
164
  chellow/templates/e/comms.html,sha256=bUXZePnMfpKk1E71qLGOSkx8r3GxdPPD_-MosIXXq4s,434
164
165
  chellow/templates/e/cop.html,sha256=ULv7ALFJHMUgPX96hQNm2irc3edtKYHS6fYAxvmzj8M,376
@@ -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=z8qJMUrGShD-7UEg7DTL5L1IacAnz7N3qoqQro30e9A,4291
330
+ chellow/templates/g/bill_import.html,sha256=iK8Bn_s_RmWr1noOdZ7f3L-VztyOl23YAnFXLK16x_o,4584
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
@@ -363,6 +364,6 @@ chellow/templates/g/supply_note_edit.html,sha256=6UQf_qbhFDys3cVsTp-c7ABWZpggW9R
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-1715858374.0.0.dist-info/METADATA,sha256=RhFE3Rlbcn0s-EnZM2XRUvEZmrglXW_AhNDP81qq__c,12205
367
- chellow-1715858374.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
368
- chellow-1715858374.0.0.dist-info/RECORD,,
367
+ chellow-1715955542.0.0.dist-info/METADATA,sha256=9p1jWHXcSPCm5spNAJsGfUTL9DSmz_kVMN9Bd36K8Hg,12205
368
+ chellow-1715955542.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
369
+ chellow-1715955542.0.0.dist-info/RECORD,,