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

@@ -1,12 +1,222 @@
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_LOOKUP = {
62
+ "STDG": "days",
63
+ "UNIT": "kwh",
64
+ }
65
+
66
+ ELEMENT_LOOKUP = {"10ANNUAL": "standing", "20RS0108": "0001", "9WANNUAL": "site_fee"}
67
+
68
+
69
+ def _handle_0460(headers, pre_record, record):
70
+ parts = _chop_record(
71
+ record,
72
+ unknown_1=12,
73
+ gbp=12,
74
+ code=8,
75
+ quantity=12,
76
+ charge=4,
77
+ unknown_2=18,
78
+ rate=16,
79
+ unknown_date=DATE_LENGTH,
80
+ another_gbp=12,
81
+ charge_description=35,
82
+ )
83
+ units = CHARGE_LOOKUP[parts["charge"]]
84
+ gbp = Decimal(parts["gbp"]) / 100
85
+ quantity = Decimal(parts["quantity"])
86
+ rate = Decimal(parts["rate"])
87
+ element_name = ELEMENT_LOOKUP[parts["code"]]
88
+ breakdown = headers["breakdown"]
89
+ breakdown[f"{element_name}-{units}"] += quantity
90
+ rate_name = f"{element_name}-rate"
91
+ if rate_name in breakdown:
92
+ rates = breakdown[rate_name]
93
+ else:
94
+ rates = breakdown[rate_name] = set()
95
+
96
+ rates.add(rate)
97
+ breakdown[f"{element_name}-gbp"] += gbp
98
+
99
+
100
+ UNITS_LOOKUP = {"KWH": "kwh"}
101
+
102
+ REGISTER_CODE_LOOKUP = {"000001": "0001"}
103
+
104
+
105
+ def _handle_0461(headers, pre_record, record):
106
+ parts = _chop_record(
107
+ record,
108
+ msn=11,
109
+ unknown_1=2,
110
+ prev_read_value=12,
111
+ pres_read_value=12,
112
+ register_code=6,
113
+ units=6,
114
+ quantity=12,
115
+ charge=6,
116
+ prev_read_type=1,
117
+ pres_read_type=1,
118
+ mpan_core=13,
119
+ mpan_top=8,
120
+ tariff_code=19,
121
+ pres_read_date=DATE_LENGTH,
122
+ prev_read_date=DATE_LENGTH,
123
+ )
124
+ mpan_core = parts["mpan_core"]
125
+ headers["mpan_core"] = mpan_core
126
+ units = UNITS_LOOKUP[parts["units"].strip()]
127
+ if units == "kwh":
128
+ headers["kwh"] += Decimal(parts["quantity"])
129
+ tpr_code = REGISTER_CODE_LOOKUP[parts["register_code"]]
130
+
131
+ headers["reads"].append(
132
+ {
133
+ "msn": parts["msn"].strip(),
134
+ "mpan": f"{parts['mpan_top']} {mpan_core}",
135
+ "coefficient": 1,
136
+ "units": units,
137
+ "tpr_code": tpr_code,
138
+ "prev_date": parse_date(parts["prev_read_date"]),
139
+ "prev_value": Decimal(parts["prev_read_value"]),
140
+ "prev_type_code": parts["prev_read_type"],
141
+ "pres_date": parse_date(parts["pres_read_date"]),
142
+ "pres_value": Decimal(parts["pres_read_value"]),
143
+ "pres_type_code": parts["pres_read_type"],
144
+ }
145
+ )
146
+
147
+
148
+ def _handle_0470(headers, pre_record, record):
149
+ pass
150
+
151
+
152
+ def _handle_1460(headers, pre_record, record):
153
+ parts = _chop_record(record, unknown_1=1, net=12, vat_rate=5, vat=12)
154
+ net = Decimal(parts["net"]) / Decimal(100)
155
+ vat_rate = int(Decimal(parts["vat_rate"]))
156
+ vat = Decimal(parts["vat"]) / Decimal(100)
157
+
158
+ vat_breakdown = headers["breakdown"]["vat"]
159
+ try:
160
+ vat_bd = vat_breakdown[vat_rate]
161
+ except KeyError:
162
+ vat_bd = vat_breakdown[vat_rate] = {"vat": Decimal("0"), "net": Decimal("0")}
163
+
164
+ vat_bd["vat"] += vat
165
+ vat_bd["net"] += net
166
+
167
+
168
+ def _handle_1500(headers, pre_record, record):
169
+ parts = _chop_record(
170
+ record,
171
+ unknown_1=8,
172
+ unknown_2=10,
173
+ net=10,
174
+ unknown_3=10,
175
+ unknown_4=20,
176
+ vat=10,
177
+ unknown_5=10,
178
+ unknown_6=20,
179
+ gross=12,
180
+ )
181
+ return {
182
+ "bill_type_code": "N",
183
+ "mpan_core": headers["mpan_core"],
184
+ "account": headers["account"],
185
+ "reference": headers["reference"],
186
+ "issue_date": headers["issue_date"],
187
+ "start_date": headers["start_date"],
188
+ "finish_date": headers["finish_date"],
189
+ "kwh": headers["kwh"],
190
+ "net": Decimal(parts["net"]),
191
+ "vat": Decimal(parts["vat"]),
192
+ "gross": Decimal(parts["gross"]),
193
+ "breakdown": headers["breakdown"],
194
+ "reads": headers["reads"],
195
+ }
196
+
197
+
198
+ def _handle_2000(headers, pre_record, record):
199
+ pass
200
+
201
+
202
+ def _handle_9999(headers, pre_record, record):
203
+ pass
204
+
205
+
206
+ LINE_HANDLERS = {
207
+ "0000": _handle_0000,
208
+ "0050": _handle_0050,
209
+ "0051": _handle_0051,
210
+ "0100": _handle_0100,
211
+ "0101": _handle_0101,
212
+ "0460": _handle_0460,
213
+ "0461": _handle_0461,
214
+ "0470": _handle_0470,
215
+ "1460": _handle_1460,
216
+ "1500": _handle_1500,
217
+ "2000": _handle_2000,
218
+ "9999": _handle_9999,
219
+ }
10
220
 
11
221
 
12
222
  class Parser:
@@ -16,41 +226,28 @@ class Parser:
16
226
 
17
227
  def make_raw_bills(self):
18
228
  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)
229
+ headers = {}
230
+ for self.line_number, line in enumerate(self.f, 1):
231
+ pre_record, record_type, record = line[:80], line[80:84], line[84:]
232
+ try:
233
+ handler = LINE_HANDLERS[record_type]
234
+ except KeyError:
235
+ raise BadRequest(
236
+ f"Record type {record_type} not recognized on line "
237
+ f"{self.line_number} {line}"
238
+ )
239
+
240
+ try:
241
+ bill = handler(headers, pre_record, record)
242
+ except BadRequest as e:
243
+ raise BadRequest(
244
+ f"Problem at line {self.line_number} {line}: {e.description}"
245
+ )
246
+ except BaseException as e:
247
+ raise Exception(
248
+ f"Problem at line {self.line_number} {line}: {e}"
249
+ ) from e
250
+ if bill is not None:
251
+ raw_bills.append(bill)
55
252
 
56
253
  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: 1716564585.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)
@@ -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=b4Tzil_iXyKGvJSSbmMtBwH-4oiwIZthnvueU7CxNwg,6875
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-1716564585.0.0.dist-info/METADATA,sha256=xaQbUGAclcjTkpSWGRS8X8pIe8pSo17WT6dFKZX4nwE,12205
368
+ chellow-1716564585.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
369
+ chellow-1716564585.0.0.dist-info/RECORD,,