chellow 1716452334.0.0__py3-none-any.whl → 1716903746.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.

@@ -179,7 +179,7 @@ def _process_batch_file(sess, batch_file, log_f):
179
179
  raise BadRequest(f"Can't find a parser with the name '{parser_name}'.")
180
180
 
181
181
  parser = imp_mod.Parser(BytesIO(data))
182
- log_f(f"Starting to parse the file with '{parser_name}'.")
182
+ log_f(f"Starting to parse the file {batch_file.filename} with '{parser_name}'.")
183
183
 
184
184
  return parser
185
185
 
@@ -1,12 +1,267 @@
1
1
  import datetime
2
+ from collections import defaultdict
2
3
  from decimal import Decimal
3
4
  from io import StringIO
4
5
 
5
- from chellow.utils import parse_mpan_core, validate_hh_start
6
+ from werkzeug.exceptions import BadRequest
6
7
 
8
+ from chellow.utils import HH, to_ct, to_utc
7
9
 
8
- def parse_date(date_string):
9
- return validate_hh_start(datetime.datetime.strptime(date_string, "%Y%m%d"))
10
+
11
+ def parse_date(date_str):
12
+ return to_utc(to_ct(parse_date_naive(date_str)))
13
+
14
+
15
+ def parse_date_naive(date_str):
16
+ return datetime.datetime.strptime(date_str, "%Y%m%d")
17
+
18
+
19
+ def _chop_record(record, **kwargs):
20
+ parts = {}
21
+ idx = 0
22
+ for name, length in kwargs.items():
23
+ parts[name] = record[idx : idx + length]
24
+ idx += length
25
+ return parts
26
+
27
+
28
+ DATE_LENGTH = 8
29
+
30
+
31
+ def _handle_0000(headers, pre_record, record):
32
+ parts = _chop_record(record, unknown_1=9, issue_date=DATE_LENGTH)
33
+ headers["issue_date"] = parse_date(parts["issue_date"])
34
+
35
+
36
+ def _handle_0050(headers, pre_record, record):
37
+ pass
38
+
39
+
40
+ def _handle_0051(headers, pre_record, record):
41
+ pass
42
+
43
+
44
+ def _handle_0100(headers, pre_record, record):
45
+ issue_date = headers["issue_date"]
46
+ headers.clear()
47
+ headers["issue_date"] = issue_date
48
+ headers["account"] = pre_record[33:41]
49
+ headers["reference"] = pre_record[41:46]
50
+ headers["kwh"] = Decimal("0")
51
+ headers["breakdown"] = defaultdict(int, {"vat": {}})
52
+ headers["reads"] = []
53
+
54
+
55
+ def _handle_0101(headers, pre_record, record):
56
+ parts = _chop_record(record, start_date=DATE_LENGTH, finish_date=DATE_LENGTH)
57
+ headers["start_date"] = parse_date(parts["start_date"])
58
+ headers["finish_date"] = to_utc(to_ct(parse_date_naive(parts["finish_date"]) - HH))
59
+
60
+
61
+ CHARGE_UNITS_LOOKUP = {
62
+ "STDG": "days",
63
+ "UNIT": "kwh",
64
+ "AVAL": "kva",
65
+ "EXAVAL": "kva",
66
+ "MD": "kw",
67
+ "LOADU": "kw",
68
+ "SAG": "days",
69
+ "TNUOS": "days",
70
+ "REAP": "kvarh",
71
+ }
72
+
73
+ ELEMENT_LOOKUP = {
74
+ "10ANNUAL": "standing",
75
+ "20RS0108": "unrestricted",
76
+ "9WANNUAL": "site_fee",
77
+ "20RS0123": "day",
78
+ "30RS0123": "night",
79
+ "90ANNUAL": "duos-fixed",
80
+ "9QANNUAL": "duos-availability",
81
+ "9UANNUAL": "duos-excess-availability",
82
+ "40ANNUAL": "maximum-demand",
83
+ "20ANNUAL": "triad",
84
+ "70ANNUAL": "elexon",
85
+ "10RS0050": "duos-red",
86
+ "20RS0050": "duos-amber",
87
+ "30RS0050": "duos-red",
88
+ "9CANNUAL": "duos-reactive",
89
+ }
90
+
91
+
92
+ def _handle_0460(headers, pre_record, record):
93
+ parts = _chop_record(
94
+ record,
95
+ unknown_1=12,
96
+ unknown_2=12,
97
+ code=8,
98
+ quantity=12,
99
+ units=22,
100
+ rate=16,
101
+ unknown_date=DATE_LENGTH,
102
+ gbp=12,
103
+ charge_description=35,
104
+ unknown_3=51,
105
+ days=2,
106
+ )
107
+ units = CHARGE_UNITS_LOOKUP[parts["units"].strip()]
108
+ gbp = Decimal(parts["gbp"]) / 100
109
+ quantity = Decimal(parts["quantity"])
110
+ rate = Decimal(parts["rate"])
111
+ element_name = ELEMENT_LOOKUP[parts["code"]]
112
+ breakdown = headers["breakdown"]
113
+ breakdown[f"{element_name}-{units}"] += quantity
114
+ rate_name = f"{element_name}-rate"
115
+ if rate_name in breakdown:
116
+ rates = breakdown[rate_name]
117
+ else:
118
+ rates = breakdown[rate_name] = set()
119
+
120
+ rates.add(rate)
121
+ breakdown[f"{element_name}-gbp"] += gbp
122
+ if element_name in ("duos-availability", "duos-excess-availability"):
123
+ breakdown[f"{element_name}-days"] += Decimal(parts["days"])
124
+
125
+
126
+ CONSUMPTION_UNITS_LOOKUP = {"KWH": "kwh", "KVA": "kva", "KVARH": "kvarh", "KW": "kw"}
127
+
128
+ REGISTER_CODE_LOOKUP = {"DAY": "00040", "NIGHT": "00206", "SINGLE": "00001"}
129
+
130
+
131
+ def _handle_0461(headers, pre_record, record):
132
+ parts = _chop_record(
133
+ record,
134
+ msn=11,
135
+ unknown_1=2,
136
+ prev_read_value=12,
137
+ pres_read_value=12,
138
+ coefficient=6,
139
+ units=6,
140
+ quantity=12,
141
+ charge=6,
142
+ prev_read_type=1,
143
+ pres_read_type=1,
144
+ mpan_core=13,
145
+ mpan_top=8,
146
+ register_code=19,
147
+ pres_read_date=DATE_LENGTH,
148
+ prev_read_date=DATE_LENGTH,
149
+ )
150
+ mpan_core = parts["mpan_core"]
151
+ headers["mpan_core"] = mpan_core
152
+ units = CONSUMPTION_UNITS_LOOKUP[parts["units"].strip()]
153
+ if units == "kwh":
154
+ headers["kwh"] += Decimal(parts["quantity"])
155
+
156
+ prev_read_date_str = parts["prev_read_date"].strip()
157
+ if len(prev_read_date_str) > 0:
158
+ tpr_code = REGISTER_CODE_LOOKUP[parts["register_code"].strip()]
159
+
160
+ headers["reads"].append(
161
+ {
162
+ "msn": parts["msn"].strip(),
163
+ "mpan": f"{parts['mpan_top']} {mpan_core}",
164
+ "coefficient": Decimal(parts["coefficient"]),
165
+ "units": units,
166
+ "tpr_code": tpr_code,
167
+ "prev_date": parse_date(parts["prev_read_date"]),
168
+ "prev_value": Decimal(parts["prev_read_value"]),
169
+ "prev_type_code": parts["prev_read_type"],
170
+ "pres_date": parse_date(parts["pres_read_date"]),
171
+ "pres_value": Decimal(parts["pres_read_value"]),
172
+ "pres_type_code": parts["pres_read_type"],
173
+ }
174
+ )
175
+
176
+
177
+ def _handle_0470(headers, pre_record, record):
178
+ pass
179
+
180
+
181
+ def _handle_1455(headers, pre_record, record):
182
+ parts = _chop_record(record, ccl_kwh=13, unknown_1=8, ccl_rate=15, ccl_gbp=13)
183
+ bd = headers["breakdown"]
184
+ bd["ccl_kwh"] += Decimal(parts["ccl_kwh"])
185
+ if "ccl_rate" in bd:
186
+ ccl_rates = bd["ccl_rate"]
187
+ else:
188
+ ccl_rates = bd["ccl_rate"] = set()
189
+
190
+ ccl_rates.add(Decimal(parts["ccl_rate"]) / Decimal("100"))
191
+ bd["ccl_gbp"] += Decimal(parts["ccl_gbp"]) / Decimal("100")
192
+
193
+
194
+ def _handle_1460(headers, pre_record, record):
195
+ parts = _chop_record(record, unknown_1=1, net=12, vat_rate=6, vat=12)
196
+ net = Decimal(parts["net"]) / Decimal(100)
197
+ vat_rate = int(Decimal(parts["vat_rate"]))
198
+ vat = Decimal(parts["vat"]) / Decimal(100)
199
+
200
+ vat_breakdown = headers["breakdown"]["vat"]
201
+ try:
202
+ vat_bd = vat_breakdown[vat_rate]
203
+ except KeyError:
204
+ vat_bd = vat_breakdown[vat_rate] = {"vat": Decimal("0"), "net": Decimal("0")}
205
+
206
+ vat_bd["vat"] += vat
207
+ vat_bd["net"] += net
208
+
209
+
210
+ def _handle_1500(headers, pre_record, record):
211
+ parts = _chop_record(
212
+ record,
213
+ unknown_1=8,
214
+ unknown_2=10,
215
+ unknown_3=10,
216
+ unknown_4=10,
217
+ unknown_5=20,
218
+ unknown_6=10,
219
+ unknown_7=10,
220
+ unknown_8=20,
221
+ gross=12,
222
+ net=12,
223
+ vat=12,
224
+ )
225
+ return {
226
+ "bill_type_code": "N",
227
+ "mpan_core": headers["mpan_core"],
228
+ "account": headers["account"],
229
+ "reference": headers["reference"],
230
+ "issue_date": headers["issue_date"],
231
+ "start_date": headers["start_date"],
232
+ "finish_date": headers["finish_date"],
233
+ "kwh": headers["kwh"],
234
+ "net": Decimal("0.00") + Decimal(parts["net"]) / Decimal("100"),
235
+ "vat": Decimal("0.00") + Decimal(parts["vat"]) / Decimal("100"),
236
+ "gross": Decimal("0.00") + Decimal(parts["gross"]) / Decimal("100"),
237
+ "breakdown": headers["breakdown"],
238
+ "reads": headers["reads"],
239
+ }
240
+
241
+
242
+ def _handle_2000(headers, pre_record, record):
243
+ pass
244
+
245
+
246
+ def _handle_9999(headers, pre_record, record):
247
+ pass
248
+
249
+
250
+ LINE_HANDLERS = {
251
+ "0000": _handle_0000,
252
+ "0050": _handle_0050,
253
+ "0051": _handle_0051,
254
+ "0100": _handle_0100,
255
+ "0101": _handle_0101,
256
+ "0460": _handle_0460,
257
+ "0461": _handle_0461,
258
+ "0470": _handle_0470,
259
+ "1455": _handle_1455,
260
+ "1460": _handle_1460,
261
+ "1500": _handle_1500,
262
+ "2000": _handle_2000,
263
+ "9999": _handle_9999,
264
+ }
10
265
 
11
266
 
12
267
  class Parser:
@@ -16,41 +271,28 @@ class Parser:
16
271
 
17
272
  def make_raw_bills(self):
18
273
  raw_bills = []
19
- for i, line in enumerate(self.f):
20
- self.line_number = i
21
- record_type = line[62:66]
22
- if record_type == "0100":
23
- account = line[33:41]
24
- reference = line[41:46]
25
- start_date = None
26
- finish_date = None
27
- net = Decimal(0)
28
- vat = Decimal(0)
29
- mpan_core = None
30
- elif record_type == "1460":
31
- net += Decimal(line[67:79]) / Decimal(100)
32
- vat += Decimal(line[85:97]) / Decimal(100)
33
- elif record_type == "0461":
34
- mpan_core = parse_mpan_core(line[135:148])
35
- elif record_type == "0101":
36
- start_date = parse_date(line[66:74])
37
- finish_date = parse_date(line[74:82])
38
- elif record_type == "1500":
39
- raw_bill = {
40
- "bill_type_code": "N",
41
- "mpan_core": mpan_core,
42
- "account": account,
43
- "reference": reference,
44
- "issue_date": start_date,
45
- "start_date": start_date,
46
- "finish_date": finish_date,
47
- "kwh": Decimal("0.00"),
48
- "net": net,
49
- "vat": vat,
50
- "gross": Decimal("0.00"),
51
- "breakdown": {},
52
- "reads": [],
53
- }
54
- raw_bills.append(raw_bill)
274
+ headers = {}
275
+ for self.line_number, line in enumerate(self.f, 1):
276
+ pre_record, record_type, record = line[:80], line[80:84], line[84:]
277
+ try:
278
+ handler = LINE_HANDLERS[record_type]
279
+ except KeyError:
280
+ raise BadRequest(
281
+ f"Record type {record_type} not recognized on line "
282
+ f"{self.line_number} {line}"
283
+ )
284
+
285
+ try:
286
+ bill = handler(headers, pre_record, record)
287
+ except BadRequest as e:
288
+ raise BadRequest(
289
+ f"Problem at line {self.line_number} {line}: {e.description}"
290
+ )
291
+ except BaseException as e:
292
+ raise Exception(
293
+ f"Problem at line {self.line_number} {line}: {e}"
294
+ ) from e
295
+ if bill is not None:
296
+ raw_bills.append(bill)
55
297
 
56
298
  return raw_bills
chellow/e/views.py CHANGED
@@ -5252,6 +5252,10 @@ def supplier_bill_import_get(import_id):
5252
5252
  fields["successful_max_registers"] = max(
5253
5253
  len(bill["reads"]) for bill in imp_fields["successful_bills"]
5254
5254
  )
5255
+ if "failed_bills" in imp_fields and len(imp_fields["failed_bills"]) > 0:
5256
+ fields["failed_max_registers"] = max(
5257
+ len(bill["reads"]) for bill in imp_fields["failed_bills"]
5258
+ )
5255
5259
  fields.update(imp_fields)
5256
5260
  fields["status"] = importer.status()
5257
5261
  return render_template(
@@ -50,7 +50,19 @@
50
50
  <th>VAT</th>
51
51
  <th>Gross</th>
52
52
  <th>Breakdown</th>
53
- <th>Reads</th>
53
+ {% for i in range(failed_max_registers) %}
54
+ <th>R{{loop.index}} MPAN</th>
55
+ <th>R{{loop.index}} Meter Serial Number</th>
56
+ <th>R{{loop.index}} Coefficient</th>
57
+ <th>R{{loop.index}} Units</th>
58
+ <th>R{{loop.index}} TPR</th>
59
+ <th>R{{loop.index}} Previous Read Date</th>
60
+ <th>R{{loop.index}} Previous Read Value</th>
61
+ <th>R{{loop.index}} Previous Read Type</th>
62
+ <th>R{{loop.index}} Present Read Date</th>
63
+ <th>R{{loop.index}} Present Read Value</th>
64
+ <th>R{{loop.index}} Present Read Type</th>
65
+ {% endfor %}
54
66
  </tr>
55
67
  </thead>
56
68
  <tbody>
@@ -87,7 +99,19 @@
87
99
  <pre>{{bill.breakdown|dumps}}</pre>
88
100
  {% endif %}
89
101
  </td>
90
- <td>{{bill.reads}}</td>
102
+ {% for read in bill.reads %}
103
+ <td>{{read.mpan}}</td>
104
+ <td>{{read.msn}}</td>
105
+ <td>{{read.coefficient}}</td>
106
+ <td>{{read.units}}</td>
107
+ <td>{{read.tpr_code}}</td>
108
+ <td>{{read.prev_date|hh_format}}</td>
109
+ <td>{{read.prev_value}}</td>
110
+ <td>{{read.prev_type_code}}</td>
111
+ <td>{{read.pres_date|hh_format}}</td>
112
+ <td>{{read.pres_value}}</td>
113
+ <td>{{read.pres_type_code}}</td>
114
+ {% endfor %}
91
115
  </tr>
92
116
  {% endfor %}
93
117
  </tbody>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: chellow
3
- Version: 1716452334.0.0
3
+ Version: 1716903746.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)
@@ -14,7 +14,7 @@ chellow/utils.py,sha256=32qPWEGzCPKPhSM7ztpzcR6ZG74sVZanScDIdK50Rq4,19308
14
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
- chellow/e/bill_importer.py,sha256=y1bpn49xDwltj0PRFowWsjAVamcD8eyxUbCTdGxEZiE,7389
17
+ chellow/e/bill_importer.py,sha256=7UcnqNlKbJc2GhW9gy8sDp9GuqambJVpZLvbafOZztA,7411
18
18
  chellow/e/bmarketidx.py,sha256=0JCBBnP3xfLq2eFjXO_u-gzHyTizab4Pp6PRK6OZLcw,7134
19
19
  chellow/e/bsuos.py,sha256=hdP9vnOJSuZl46OAkJeUg1XJYvYIBj4J6Sqce1Hy9Vs,15542
20
20
  chellow/e/ccl.py,sha256=30dh_SvlgzsTQPPAJNZWILaMvbeDsv9-P-S1JxS5_SQ,3184
@@ -38,7 +38,7 @@ chellow/e/system_price.py,sha256=6w5J7bzwFAZubE2zdOFRiS8IIrVP8hkoIOaG2yCt-Ic,623
38
38
  chellow/e/tlms.py,sha256=M33D6YpMixu2KkwSCzDRM3kThLgShg8exp63Obo75l8,8905
39
39
  chellow/e/tnuos.py,sha256=XseYztPUsQXNKuBmystO2kzzwAG9ehCZgpGBTdgSk-A,4313
40
40
  chellow/e/triad.py,sha256=lIQj7EdUrcFwEqleuHZXYU_bfzIwNOqUVVxB3NPQt4A,13710
41
- chellow/e/views.py,sha256=es1lQWgZjCcHQ7AR_98SM9hLJjX8UUarePuBHnkkJ_4,215037
41
+ chellow/e/views.py,sha256=RQLiAzzWm5RUumvhR1rub1yFTMZ9uksqJIc1IShnXsQ,215256
42
42
  chellow/e/bill_parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  chellow/e/bill_parsers/activity_mop_stark_xlsx.py,sha256=UgWXDPzQkQghyj_lfgBqoSJpHB-t-qOdSaB8qY6GLog,4071
44
44
  chellow/e/bill_parsers/annual_mop_stark_xlsx.py,sha256=-HMoIfa_utXYKA44RuC0Xqv3vd2HLeQU_4P0iBUd3WA,4219
@@ -51,7 +51,7 @@ chellow/e/bill_parsers/gdf_csv.py,sha256=ZfK3Oc6oP28p_P9DIevLNB_zW2WLcEJ3Lvb1gL3
51
51
  chellow/e/bill_parsers/haven_csv.py,sha256=0uENq8IgVNqdxfBQMBxLTSZWCOuDHXZC0xzk52SbfyE,13652
52
52
  chellow/e/bill_parsers/haven_edi.py,sha256=YGPHRxPOhje9s32jqPHHELni2tooOYj3cMC_qaZVPq4,16107
53
53
  chellow/e/bill_parsers/haven_edi_tprs.py,sha256=ZVX9CCqUybsot_Z0BEOJPvl9x5kSr7fEWyuJXvZDcz4,11841
54
- chellow/e/bill_parsers/mm.py,sha256=P4CdkskDrjmFMSDp0ehE_ichTBHGywbT_dckMxuAqQQ,1929
54
+ chellow/e/bill_parsers/mm.py,sha256=DEy-W-Z03TdfUjtFUNk48h5nEAaXKV3etfrPZ-z001Q,8494
55
55
  chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py,sha256=yogXTuQHGRL7IiqvRWr2C9V24ez1j9Yx0128UygPE_k,4723
56
56
  chellow/e/bill_parsers/settlement_dc_stark_xlsx.py,sha256=gKeYMdUO4bVycV8n1lWs5AIfF3bHZLkM6tkasD4rhHs,6239
57
57
  chellow/e/bill_parsers/sse_edi.py,sha256=L85DOfNkqexeEIEr8pCBn_2sHJI-zEaw6cogpE3YyYM,15204
@@ -299,7 +299,7 @@ chellow/templates/e/supplier_batches.html,sha256=ij8LuHc2HzxovBn34aHCMra4pm3Himk
299
299
  chellow/templates/e/supplier_bill.html,sha256=7q79XfUSFPLR5npIXhRpTciQ9rGtNHhp9ryFZWXBhPc,4142
300
300
  chellow/templates/e/supplier_bill_add.html,sha256=BsD-Zh7d9auiqJ61VPHiQrP8u8rTcw09mEKuKH9dxaA,2100
301
301
  chellow/templates/e/supplier_bill_edit.html,sha256=oxZrMcMwrvluJSPxD4yfM9mWNeugoguAwT_ai9Ynl88,2732
302
- chellow/templates/e/supplier_bill_import.html,sha256=XSDURJKvXvI3ee15VqYoUpmF8Ng61Puybwexzk1AFF0,4291
302
+ chellow/templates/e/supplier_bill_import.html,sha256=2_VvBoNE838UwuN0AiMwIrzqxQmWBo5DGY0lY833Bpk,5265
303
303
  chellow/templates/e/supplier_bill_imports.html,sha256=9iTNGWKn9XjQTBP1Sepbo0QVNlgKh7EfizXapam7I6s,9356
304
304
  chellow/templates/e/supplier_contract.html,sha256=oVGrYGIbCb-7MmpFQaXeTNRKBKy8GoGCWICWgo5E4xI,2863
305
305
  chellow/templates/e/supplier_contract_add.html,sha256=gsozEtF24lzYi_Bb4LTenvh62tCt7dQ4CwaIz7rFck4,899
@@ -364,6 +364,6 @@ chellow/templates/g/supply_note_edit.html,sha256=6UQf_qbhFDys3cVsTp-c7ABWZpggW9R
364
364
  chellow/templates/g/supply_notes.html,sha256=WR3YwGh_qqTklSJ7JqWX6BKBc9rk_jMff4RiWZiF2CM,936
365
365
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
366
366
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
367
- chellow-1716452334.0.0.dist-info/METADATA,sha256=pjgx-WKJRzgK4cjpaGC2sDSgRDU2AQS0gxnApoP6NnI,12205
368
- chellow-1716452334.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
369
- chellow-1716452334.0.0.dist-info/RECORD,,
367
+ chellow-1716903746.0.0.dist-info/METADATA,sha256=1b3az19NrZfzrNS1rPZPOjvDnuejOViqXe0ubM4A0-Q,12205
368
+ chellow-1716903746.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
369
+ chellow-1716903746.0.0.dist-info/RECORD,,