chellow 1745313690.0.0__py3-none-any.whl → 1746445803.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/e/bill_parsers/mm.py +3 -0
- chellow/gas/bill_parser_bgs_xlsx.py +271 -0
- {chellow-1745313690.0.0.dist-info → chellow-1746445803.0.0.dist-info}/METADATA +1 -1
- {chellow-1745313690.0.0.dist-info → chellow-1746445803.0.0.dist-info}/RECORD +5 -4
- {chellow-1745313690.0.0.dist-info → chellow-1746445803.0.0.dist-info}/WHEEL +0 -0
chellow/e/bill_parsers/mm.py
CHANGED
|
@@ -36,6 +36,8 @@ def _handle_0000(headers, pre_record, record):
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def _handle_0050(headers, pre_record, record):
|
|
39
|
+
pass
|
|
40
|
+
"""
|
|
39
41
|
parts = _chop_record(
|
|
40
42
|
record,
|
|
41
43
|
issue_date=DATE_LENGTH,
|
|
@@ -46,6 +48,7 @@ def _handle_0050(headers, pre_record, record):
|
|
|
46
48
|
late_payment=12,
|
|
47
49
|
)
|
|
48
50
|
headers["late_payment"] = Decimal(parts["late_payment"]) / Decimal(100)
|
|
51
|
+
"""
|
|
49
52
|
|
|
50
53
|
|
|
51
54
|
def _handle_0051(headers, pre_record, record):
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from datetime import datetime as Datetime
|
|
3
|
+
from decimal import Decimal, InvalidOperation
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
|
|
7
|
+
from dateutil.relativedelta import relativedelta
|
|
8
|
+
|
|
9
|
+
from openpyxl import load_workbook
|
|
10
|
+
|
|
11
|
+
from werkzeug.exceptions import BadRequest
|
|
12
|
+
|
|
13
|
+
from chellow.utils import to_ct, to_utc
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Title(Enum):
|
|
17
|
+
CUSTOMER = auto()
|
|
18
|
+
PRODUCT = auto()
|
|
19
|
+
BROKER_NAME = auto()
|
|
20
|
+
ACCOUNT = auto()
|
|
21
|
+
MPRN = auto()
|
|
22
|
+
BILL = auto()
|
|
23
|
+
SUPPLY_ADDRESS = auto()
|
|
24
|
+
BILL_DATE = auto()
|
|
25
|
+
BILLING_PERIOD = auto()
|
|
26
|
+
CHARGE_TYPE = auto()
|
|
27
|
+
CHARGE_PERIOD_FROM = auto()
|
|
28
|
+
CHARGE_PERIOD_END = auto()
|
|
29
|
+
QUANTITY = auto()
|
|
30
|
+
QUNIT = auto()
|
|
31
|
+
CHARGE = auto()
|
|
32
|
+
CUNIT = auto()
|
|
33
|
+
TOTAL = auto()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
COLUMNS = {
|
|
37
|
+
Title.CUSTOMER: ["Customer"],
|
|
38
|
+
Title.PRODUCT: ["Product"],
|
|
39
|
+
Title.BROKER_NAME: ["Broker Name"],
|
|
40
|
+
Title.ACCOUNT: ["Account"],
|
|
41
|
+
Title.MPRN: ["MPRN"],
|
|
42
|
+
Title.BILL: ["Bill"],
|
|
43
|
+
Title.SUPPLY_ADDRESS: ["Supply Address"],
|
|
44
|
+
Title.BILL_DATE: ["Bill Date"],
|
|
45
|
+
Title.BILLING_PERIOD: ["Billing Period"],
|
|
46
|
+
Title.CHARGE_TYPE: ["Charge Type"],
|
|
47
|
+
Title.CHARGE_PERIOD_FROM: ["Charge Period From"],
|
|
48
|
+
Title.CHARGE_PERIOD_END: ["Charge Period End"],
|
|
49
|
+
Title.QUANTITY: ["Quantity"],
|
|
50
|
+
Title.QUNIT: ["QUnit"],
|
|
51
|
+
Title.CHARGE: ["Charge"],
|
|
52
|
+
Title.CUNIT: ["CUnit"],
|
|
53
|
+
Title.TOTAL: ["Total"],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def make_column_map(title_row):
|
|
58
|
+
titles = [cell.value for cell in title_row]
|
|
59
|
+
column_map = {}
|
|
60
|
+
for title, title_names in COLUMNS.items():
|
|
61
|
+
idx = None
|
|
62
|
+
for title_name in title_names:
|
|
63
|
+
try:
|
|
64
|
+
idx = titles.index(title_name)
|
|
65
|
+
except ValueError:
|
|
66
|
+
pass
|
|
67
|
+
if idx is None:
|
|
68
|
+
raise BadRequest(f"For the title {title} a column can't be found")
|
|
69
|
+
|
|
70
|
+
column_map[title] = idx + 1
|
|
71
|
+
return column_map
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_date_naive(sheet, row, col):
|
|
75
|
+
value = get_value(sheet, row, col)
|
|
76
|
+
if value in ("", None):
|
|
77
|
+
return None
|
|
78
|
+
elif not isinstance(value, Datetime):
|
|
79
|
+
raise BadRequest(
|
|
80
|
+
f"Problem reading {value} as a timestamp at row {row} col {col}."
|
|
81
|
+
)
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_date(sheet, row, col_name):
|
|
86
|
+
dt = get_date_naive(sheet, row, col_name)
|
|
87
|
+
return None if dt is None else to_utc(to_ct(dt))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_value(sheet, row, col):
|
|
91
|
+
try:
|
|
92
|
+
return sheet.cell(row=row, column=col).value
|
|
93
|
+
except IndexError:
|
|
94
|
+
raise BadRequest(
|
|
95
|
+
f"Can't find the cell at row {row} and col {col} on sheet {sheet}."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_str(sheet, row, col):
|
|
100
|
+
value = get_value(sheet, row, col)
|
|
101
|
+
if value is None:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
return value.strip()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_dec(sheet, row, col):
|
|
108
|
+
value = get_value(sheet, row, col)
|
|
109
|
+
if value in ("", None):
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
return Decimal(str(value))
|
|
114
|
+
except InvalidOperation as e:
|
|
115
|
+
raise BadRequest(
|
|
116
|
+
f"Problem parsing the number '{value}' at row {row} col {col}. {e}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_int(sheet, row, col):
|
|
121
|
+
value = get_value(sheet, row, col)
|
|
122
|
+
try:
|
|
123
|
+
return int(value)
|
|
124
|
+
except ValueError as e:
|
|
125
|
+
raise BadRequest(
|
|
126
|
+
f"Problem parsing the integer '{value}' at row {row} col {col}. {e}"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
def _bd_add(bd, el_name, val):
|
|
132
|
+
if el_name.split("-")[-1] in ("rate", "kva"):
|
|
133
|
+
if el_name not in bd:
|
|
134
|
+
bd[el_name] = set()
|
|
135
|
+
bd[el_name].add(val)
|
|
136
|
+
else:
|
|
137
|
+
if el_name not in bd:
|
|
138
|
+
bd[el_name] = 0
|
|
139
|
+
try:
|
|
140
|
+
bd[el_name] += val
|
|
141
|
+
except TypeError as e:
|
|
142
|
+
raise BadRequest(
|
|
143
|
+
f"Problem with element name {el_name} and value '{val}': {e}"
|
|
144
|
+
)
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
ELEMENT_LOOKUP = {
|
|
148
|
+
"Management Fee": "admin_variable",
|
|
149
|
+
"LDZ Customer Capacity": "dn_customer_capacity_fixed",
|
|
150
|
+
"LDZ System Capacity": "dn_system_capacity_fixed",
|
|
151
|
+
"LDZ System Commodity": "dn_system_capacity",
|
|
152
|
+
"Metering Charges": "metering",
|
|
153
|
+
"NTS Exit Capacity (ECN)": "dn_ecn_fixed",
|
|
154
|
+
"NTS SO Exit": "so_exit_commodity",
|
|
155
|
+
"NTS TO Exit": "to_exit_commodity",
|
|
156
|
+
"Unidentified Gas": "ug",
|
|
157
|
+
"WAP": "wap",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
QUNIT_LOOKUP = {
|
|
161
|
+
"kWh": "kwh",
|
|
162
|
+
"days": "days",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _parse_row(bills, sheet, row, title_row):
|
|
167
|
+
column_map = make_column_map(title_row)
|
|
168
|
+
mprn = get_value(sheet, row, column_map[Title.MPRN])
|
|
169
|
+
reference = get_value(sheet, row, column_map[Title.BILL])
|
|
170
|
+
account = get_value(sheet, row, column_map[Title.ACCOUNT])
|
|
171
|
+
issue_date = get_date(sheet, row, column_map[Title.BILL_DATE])
|
|
172
|
+
start_date = get_date(sheet, row, column_map[Title.CHARGE_PERIOD_FROM])
|
|
173
|
+
finish_date = to_utc(
|
|
174
|
+
to_ct(get_date_naive(sheet, row, column_map[Title.CHARGE_PERIOD_END]))
|
|
175
|
+
+ relativedelta(hours=23, minutes=30)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
mprn_values = bills[mprn]
|
|
180
|
+
except KeyError:
|
|
181
|
+
mprn_values = bills[mprn] = {}
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
start_date_values = mprn_values[start_date]
|
|
185
|
+
except KeyError:
|
|
186
|
+
start_date_values = mprn_values[start_date] = {}
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
bill = start_date_values[finish_date]
|
|
190
|
+
except KeyError:
|
|
191
|
+
bill = start_date_values[finish_date] = {
|
|
192
|
+
"bill_type_code": "N",
|
|
193
|
+
"mprn": mprn,
|
|
194
|
+
"reference": reference,
|
|
195
|
+
"account": account,
|
|
196
|
+
"issue_date": issue_date,
|
|
197
|
+
"start_date": start_date,
|
|
198
|
+
"finish_date": finish_date,
|
|
199
|
+
"kwh": Decimal("0"),
|
|
200
|
+
"net_gbp": Decimal("0.00"),
|
|
201
|
+
"vat_gbp": Decimal("0.00"),
|
|
202
|
+
"gross_gbp": Decimal("0.00"),
|
|
203
|
+
"breakdown": {},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
bd = bill["breakdown"]
|
|
207
|
+
element_desc = get_value(sheet, row, column_map[Title.CHARGE_TYPE])
|
|
208
|
+
quantity = get_dec(sheet, row, column_map[Title.QUANTITY])
|
|
209
|
+
qunit = get_value(sheet, row, column_map[Title.QUNIT])
|
|
210
|
+
charge = get_dec(sheet, row, column_map[Title.CHARGE]) / Decimal("100")
|
|
211
|
+
total = get_dec(sheet, row, column_map[Title.TOTAL])
|
|
212
|
+
if element_desc.startswith("20% VAT on "):
|
|
213
|
+
bill["net_gbp"] += quantity
|
|
214
|
+
bill["vat_gbp"] += total
|
|
215
|
+
bill["gross_gbp"] += quantity + total
|
|
216
|
+
else:
|
|
217
|
+
element_name = ELEMENT_LOOKUP[element_desc]
|
|
218
|
+
if element_name == "admin_variable":
|
|
219
|
+
bill["kwh"] += quantity
|
|
220
|
+
bd[f"{element_name}_gbp"] = total
|
|
221
|
+
element_qunit = QUNIT_LOOKUP[qunit]
|
|
222
|
+
bd[f"{element_name}_{element_qunit}"] = quantity
|
|
223
|
+
bd[f"{element_name}_rate"] = charge
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _make_raw_bills(sheet):
|
|
227
|
+
bills = {}
|
|
228
|
+
rows = tuple(sheet.rows)
|
|
229
|
+
title_row = rows[1]
|
|
230
|
+
for row_index, row in enumerate(rows[2:], start=3):
|
|
231
|
+
val = row[0].value
|
|
232
|
+
if val not in (None, ""):
|
|
233
|
+
try:
|
|
234
|
+
_parse_row(bills, sheet, row_index, title_row)
|
|
235
|
+
except BadRequest as e:
|
|
236
|
+
raise BadRequest(f"On row {row_index + 1}: {e.description}")
|
|
237
|
+
print("bills", bills)
|
|
238
|
+
|
|
239
|
+
raw_bills = []
|
|
240
|
+
for mprn, mprn_values in bills.items():
|
|
241
|
+
for period_stat, period_start_values in mprn_values.items():
|
|
242
|
+
for period_finish, bill in period_start_values.items():
|
|
243
|
+
raw_bills.append(bill)
|
|
244
|
+
|
|
245
|
+
return raw_bills
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class Parser:
|
|
249
|
+
def __init__(self, f):
|
|
250
|
+
self.book = load_workbook(BytesIO(f), data_only=True)
|
|
251
|
+
self.sheet = self.book.worksheets[0]
|
|
252
|
+
|
|
253
|
+
self.last_line = None
|
|
254
|
+
lines = (self._set_last_line(i, l) for i, l in enumerate(f))
|
|
255
|
+
self.reader = csv.reader(lines, skipinitialspace=True)
|
|
256
|
+
self._line_number = None
|
|
257
|
+
self._title_line = None
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def line_number(self):
|
|
261
|
+
return None if self._line_number is None else self._line_number + 1
|
|
262
|
+
|
|
263
|
+
def _set_last_line(self, i, line):
|
|
264
|
+
self._line_numer = i
|
|
265
|
+
self.last_line = line
|
|
266
|
+
if i == 0:
|
|
267
|
+
self._title_line = line
|
|
268
|
+
return line
|
|
269
|
+
|
|
270
|
+
def make_raw_bills(self):
|
|
271
|
+
return _make_raw_bills(self.sheet)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chellow
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1746445803.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)
|
|
@@ -59,12 +59,13 @@ chellow/e/bill_parsers/gdf_csv.py,sha256=ZfK3Oc6oP28p_P9DIevLNB_zW2WLcEJ3Lvb1gL3
|
|
|
59
59
|
chellow/e/bill_parsers/haven_csv.py,sha256=0uENq8IgVNqdxfBQMBxLTSZWCOuDHXZC0xzk52SbfyE,13652
|
|
60
60
|
chellow/e/bill_parsers/haven_edi.py,sha256=YGPHRxPOhje9s32jqPHHELni2tooOYj3cMC_qaZVPq4,16107
|
|
61
61
|
chellow/e/bill_parsers/haven_edi_tprs.py,sha256=ZVX9CCqUybsot_Z0BEOJPvl9x5kSr7fEWyuJXvZDcz4,11841
|
|
62
|
-
chellow/e/bill_parsers/mm.py,sha256=
|
|
62
|
+
chellow/e/bill_parsers/mm.py,sha256=i1TcEo3eY6_i_1DfRP3fIX7kIsx5cVx_FZPFG6S-FNo,11252
|
|
63
63
|
chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py,sha256=YvQ0Q6HlZ4XSk6Phx1UWaTSj8FREVjwQe8nrpiLVzbU,5458
|
|
64
64
|
chellow/e/bill_parsers/settlement_dc_stark_xlsx.py,sha256=osCpUYUdLcPtlo7ngXWGw0ImnxssLa1fOSejMwe51-k,6381
|
|
65
65
|
chellow/e/bill_parsers/sse_edi.py,sha256=L85DOfNkqexeEIEr8pCBn_2sHJI-zEaw6cogpE3YyYM,15204
|
|
66
66
|
chellow/e/bill_parsers/sww_xls.py,sha256=QEjiuvwvr5FuWCfqqVw8LaA_vZyAKsvRAS5fw3xtFhM,7533
|
|
67
67
|
chellow/gas/bill_import.py,sha256=w0lPgK_Drzh8rtnEBQe3qFuxrgzZ6qQSgpaGrrGznMU,6549
|
|
68
|
+
chellow/gas/bill_parser_bgs_xlsx.py,sha256=PxjFMEB91QcGvyarCc9qHfO3-Rs_Twdo9iJU9uBYfa4,7834
|
|
68
69
|
chellow/gas/bill_parser_csv.py,sha256=Ecdy-apFT-mWAxddAsM4k1s-9-FpIaOfjP0oFc0rdQg,5557
|
|
69
70
|
chellow/gas/bill_parser_engie_edi.py,sha256=Ko0vZP-QdVQ1uuhS_5cdrii60_cM4b_LFJMoY0pZqnk,8950
|
|
70
71
|
chellow/gas/bill_parser_total_edi.py,sha256=bMAeIkzHwNhv0qdKYXtRl-tzUUYtjNkbM3PMl3NurFc,11225
|
|
@@ -382,6 +383,6 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
|
|
|
382
383
|
chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
|
|
383
384
|
chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
|
|
384
385
|
chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
|
|
385
|
-
chellow-
|
|
386
|
-
chellow-
|
|
387
|
-
chellow-
|
|
386
|
+
chellow-1746445803.0.0.dist-info/METADATA,sha256=Ktp_Yf29rGg67hVLzXqCvgIBzYl3qsnyQN5fjOLS0Hk,12238
|
|
387
|
+
chellow-1746445803.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
388
|
+
chellow-1746445803.0.0.dist-info/RECORD,,
|
|
File without changes
|