chellow 1745313690.0.0__py3-none-any.whl → 1745911639.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.

@@ -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: 1745313690.0.0
3
+ Version: 1745911639.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)
@@ -65,6 +65,7 @@ chellow/e/bill_parsers/settlement_dc_stark_xlsx.py,sha256=osCpUYUdLcPtlo7ngXWGw0
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-1745313690.0.0.dist-info/METADATA,sha256=6L9prKczSdFxE1p_b8WJ_f16sePhQBrdB5ZLbBmbnTk,12238
386
- chellow-1745313690.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
387
- chellow-1745313690.0.0.dist-info/RECORD,,
386
+ chellow-1745911639.0.0.dist-info/METADATA,sha256=ayBjqwEd2px2L8nUYB1t63nDxqnf-NrSJIhWkln42HA,12238
387
+ chellow-1745911639.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
388
+ chellow-1745911639.0.0.dist-info/RECORD,,