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 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):
@@ -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)
@@ -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] = round(
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 "delete" in request.values:
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
- {% for read in failed_bills[0]['reads'] %}
56
- {% for k in read|sort %}
57
- <th>{{k}}</th>
58
- {% endfor %}
59
- {% endfor %}
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
- {% for k, v in read|dictsort %}
82
- <td>{{v}}</td>
83
- {% endfor %}
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
- {% if request.method == 'GET' and request.values.delete %}
14
- <form method="post" action="/g/supplies/{{g_supply.id}}/edit">
15
- <fieldset>
16
- <legend>Delete</legend>
17
- <p>Are you sure you want to delete this supply?</p>
18
- <input type="submit" name="delete" value="Delete">
19
- </fieldset>
20
- </form>
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
- <p>
23
- <a href="/g/supplies/{{g_supply.id}}">Cancel</a>
24
- </p>
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
- {% else %}
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
- <form method="post" action="/g/supplies/{{g_supply.id}}/edit">
29
- <fieldset>
30
- <legend>Update this supply</legend>
31
- <label>Name</label> {{input_text('name', g_supply.name)}}
32
- <label>MPRN</label> {{input_text('mprn', g_supply.mprn)}}
33
- <label>Exit Zone</label>
34
- <select name="g_exit_zone_id">
35
- {% for g_exit_zone in g_exit_zones %}
36
- {{input_option( 'g_exit_zone_id', g_exit_zone.id, g_exit_zone.code)}}
37
- {% endfor %}
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
- <th>Start date</th>
63
- <th>Finish date</th>
56
+ <td>{{g_era.start_date|hh_format}}</td>
57
+ <td>{{g_era.finish_date|hh_format}}</td>
64
58
  </tr>
65
- </thead>
66
- <tbody>
67
- {% for g_era in g_eras %}
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
- 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: 1715873476.0.0
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=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_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=PhaCWDslhEzDmuCu5PMt3IpFANm27OO1bupq3yQCmc0,25518
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=YFP8w0Y1PeH6tb2pPm4JTkUTmQw1AuYeO4pFXcf-1bY,56389
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=z8qJMUrGShD-7UEg7DTL5L1IacAnz7N3qoqQro30e9A,4291
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=v4HEWn97phGDgkDxi95VJg7mnZ9nF08WtxCAMH7Nbng,2022
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-1715873476.0.0.dist-info/METADATA,sha256=5qysvugFv1uEf-D4XOI0puLlQnhADi536FsnFnyivXU,12205
367
- chellow-1715873476.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
368
- chellow-1715873476.0.0.dist-info/RECORD,,
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,,