chellow 1744720514.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.

@@ -59,7 +59,7 @@ def _handle_0100(headers, pre_record, record):
59
59
  headers["issue_date"] = issue_date
60
60
  if late_payment is not None:
61
61
  headers["late_payment"] = late_payment
62
- headers["account"] = pre_record[33:41]
62
+ headers["account"] = pre_record[43:52]
63
63
  headers["reference"] = pre_record[41:46]
64
64
  headers["kwh"] = Decimal("0")
65
65
  headers["breakdown"] = defaultdict(int, {"vat": {}})
@@ -4,6 +4,7 @@ from openpyxl import load_workbook
4
4
 
5
5
  from werkzeug.exceptions import BadRequest
6
6
 
7
+ from chellow.e.computer import hh_rate
7
8
  from chellow.models import Session
8
9
  from chellow.utils import ct_datetime, parse_mpan_core, to_utc
9
10
 
@@ -16,12 +17,6 @@ def get_start_date(title_row, row, name):
16
17
  return to_utc(get_ct_date(title_row, row, name))
17
18
 
18
19
 
19
- def get_finish_date(title_row, row, name):
20
- d = get_ct_date(title_row, row, name)
21
-
22
- return to_utc(ct_datetime(d.year, d.month, d.day, 23, 30))
23
-
24
-
25
20
  def get_value(title_row, row, name):
26
21
  idx = None
27
22
  name = name.strip().lower()
@@ -61,9 +56,6 @@ def get_int(title_row, row, name):
61
56
  return int(get_value(title_row, row, name))
62
57
 
63
58
 
64
- METER_RATE = Decimal("60.00")
65
-
66
-
67
59
  class Parser:
68
60
  def __init__(self, f):
69
61
  self.book = load_workbook(f)
@@ -86,6 +78,7 @@ class Parser:
86
78
 
87
79
  def make_raw_bills(self):
88
80
  row_index = None
81
+ caches = {}
89
82
  try:
90
83
  with Session() as sess:
91
84
  bills = []
@@ -101,14 +94,30 @@ class Parser:
101
94
  mpan_core = parse_mpan_core(
102
95
  str(get_int(title_row, row, "mpan ref"))
103
96
  )
104
- start_date = get_start_date(title_row, row, "start")
97
+ start_date_ct = get_ct_date(title_row, row, "start")
98
+ start_date = to_utc(start_date_ct)
105
99
  issue_date = start_date
106
- finish_date = get_finish_date(title_row, row, "end")
100
+ finish_date_ct = get_ct_date(title_row, row, "end")
101
+ finish_date = to_utc(
102
+ ct_datetime(
103
+ finish_date_ct.year,
104
+ finish_date_ct.month,
105
+ finish_date_ct.day,
106
+ 23,
107
+ 30,
108
+ )
109
+ )
107
110
  check = get_str(title_row, row, "check")
108
111
  if check != "Billed":
109
112
  continue
110
-
111
- net = METER_RATE / 12
113
+ rates = hh_rate(sess, caches, 0, start_date)
114
+ meter_rate = rates["annual_rates"]["non_settlement"]["*"]["IP"][
115
+ "*"
116
+ ]["gbp_per_meter"]
117
+ months = (finish_date_ct.year - start_date_ct.year) * 12 + (
118
+ finish_date_ct.month - start_date_ct.month + 1
119
+ )
120
+ net = round(Decimal(float(meter_rate) / 12 * months), 2)
112
121
  vat = round(net * Decimal("0.2"), 2)
113
122
 
114
123
  breakdown = {
@@ -116,7 +125,8 @@ class Parser:
116
125
  "cop": ["5"],
117
126
  "settlement-status": ["non_settlement"],
118
127
  "msn": [msn],
119
- "meter-rate": [METER_RATE],
128
+ "meter-rate": [meter_rate],
129
+ "months": months,
120
130
  "meter-gbp": net,
121
131
  }
122
132
 
@@ -650,15 +650,15 @@ def update_vls(sess, logger, vls, dno_code, fy_start, rs_finish):
650
650
  llfc = sess.execute(q).scalar_one_or_none()
651
651
 
652
652
  if llfc is None:
653
- raise BadRequest(
653
+ logger(
654
654
  f"There is no LLFC with the code '{vl_code}' associated with the DNO "
655
- f"{dno.code} from {hh_format(fy_start)} to {hh_format(rs_finish)}."
655
+ f"{dno.dno_code} from {hh_format(fy_start)} to {hh_format(rs_finish)}."
656
656
  )
657
-
658
- vl_voltage_level = VoltageLevel.get_by_code(sess, vl["voltage_level"])
659
- llfc.voltage_level = vl_voltage_level
660
-
661
- llfc.is_substation = vl["is_substation"]
662
- if sess.is_modified(llfc):
663
- logger(f"Updated LLFC {llfc.code} of DNO {dno_code}")
664
- sess.flush()
657
+ else:
658
+ vl_voltage_level = VoltageLevel.get_by_code(sess, vl["voltage_level"])
659
+ llfc.voltage_level = vl_voltage_level
660
+
661
+ llfc.is_substation = vl["is_substation"]
662
+ if sess.is_modified(llfc):
663
+ logger(f"Updated LLFC {llfc.code} of DNO {dno_code}")
664
+ sess.flush()
@@ -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)
@@ -0,0 +1,158 @@
1
+ import csv
2
+ import threading
3
+ import traceback
4
+
5
+ from flask import g, redirect
6
+
7
+
8
+ from sqlalchemy import null, or_, select
9
+ from sqlalchemy.orm import joinedload
10
+
11
+ from chellow.dloads import open_file
12
+ from chellow.models import Batch, Bill, Contract, Era, RSession, ReportRun, User
13
+ from chellow.utils import (
14
+ c_months_u,
15
+ csv_make_val,
16
+ hh_max,
17
+ hh_min,
18
+ hh_range,
19
+ req_date,
20
+ req_int,
21
+ )
22
+
23
+
24
+ def content(user_id, report_run_id, contract_id, months_length, finish_date):
25
+ f = writer = None
26
+ try:
27
+ with RSession() as sess:
28
+ caches = {}
29
+ contract = Contract.get_by_id(sess, contract_id)
30
+ user = User.get_by_id(sess, user_id)
31
+ f = open_file(
32
+ f"missing_bills_{contract.id}.csv", user, mode="w", newline=""
33
+ )
34
+ writer = csv.writer(f, lineterminator="\n")
35
+ titles = (
36
+ "contract_name",
37
+ "month_start",
38
+ "month_finish",
39
+ "site_code",
40
+ "site_name",
41
+ "imp_mpan_core",
42
+ "exp_mpan_core",
43
+ "account",
44
+ )
45
+ writer.writerow(titles)
46
+
47
+ months = list(
48
+ c_months_u(
49
+ finish_year=finish_date.year,
50
+ finish_month=finish_date.month,
51
+ months=months_length,
52
+ )
53
+ )
54
+
55
+ for month_start, month_finish in months:
56
+ missing_bills = {}
57
+ missing_account = {}
58
+ account_missing_tuple = hh_range(caches, month_start, month_finish)
59
+ for era in sess.scalars(
60
+ select(Era)
61
+ .where(
62
+ Era.start_date <= month_finish,
63
+ or_(Era.finish_date == null(), Era.finish_date >= month_start),
64
+ or_(
65
+ Era.mop_contract == contract,
66
+ Era.dc_contract == contract,
67
+ Era.imp_supplier_contract == contract,
68
+ Era.exp_supplier_contract == contract,
69
+ ),
70
+ )
71
+ .options(joinedload(Era.supply))
72
+ ):
73
+ chunk_start = hh_max(era.start_date, month_start)
74
+ chunk_finish = hh_min(era.finish_date, month_finish)
75
+ missing_set = set(hh_range(caches, chunk_start, chunk_finish))
76
+ if era.mop_contract == contract:
77
+ account = era.mop_account
78
+ elif era.dc_contract == contract:
79
+ account = era.dc_account
80
+ elif era.imp_supplier_contract == contract:
81
+ account = era.imp_supplier_account
82
+ elif era.imp_supplier_contract == contract:
83
+ account = era.exp_supplier_account
84
+
85
+ try:
86
+ account_missing_set = missing_account[account]
87
+ except KeyError:
88
+ account_missing_set = missing_account[account] = set(
89
+ account_missing_tuple
90
+ )
91
+ supply = era.supply
92
+
93
+ for bill in sess.scalars(
94
+ select(Bill)
95
+ .join(Batch)
96
+ .where(
97
+ Batch.contract == contract,
98
+ Bill.supply == supply,
99
+ Bill.start_date <= chunk_finish,
100
+ Bill.finish_date >= chunk_start,
101
+ )
102
+ ):
103
+ found_set = set(
104
+ hh_range(caches, bill.start_date, bill.finish_date)
105
+ )
106
+ missing_set.difference_update(found_set)
107
+ account_missing_set.difference_update(found_set)
108
+
109
+ if len(missing_set) > 0:
110
+ site = era.get_physical_site(sess)
111
+
112
+ values = {
113
+ "contract_id": contract.id,
114
+ "contract_name": contract.name,
115
+ "month_start": month_start,
116
+ "month_finish": month_finish,
117
+ "era_id": era.id,
118
+ "supply_id": supply.id,
119
+ "imp_mpan_core": era.imp_mpan_core,
120
+ "exp_mpan_core": era.exp_mpan_core,
121
+ "site_id": site.id,
122
+ "site_code": site.code,
123
+ "site_name": site.name,
124
+ "account": account,
125
+ "market_role_code": contract.market_role.code,
126
+ }
127
+ missing_bills[era.id] = values
128
+ for era_id, values in missing_bills.items():
129
+ if len(missing_account[values["account"]]) > 0:
130
+ writer.writerow(csv_make_val(values[t]) for t in titles)
131
+ ReportRun.w_insert_row(report_run_id, "", titles, values, {})
132
+
133
+ except BaseException:
134
+ msg = traceback.format_exc()
135
+ print(msg)
136
+ if writer is not None:
137
+ writer.writerow([msg])
138
+ finally:
139
+ ReportRun.w_update(report_run_id, "finished")
140
+ if f is not None:
141
+ f.close()
142
+
143
+
144
+ def do_get(sess):
145
+ contract_id = req_int("contract_id")
146
+ months = req_int("months")
147
+ finish_date = req_date("finish", resolution="month")
148
+ report_run = ReportRun.insert(
149
+ sess,
150
+ "missing_e_bills",
151
+ g.user,
152
+ "missing_e_bills",
153
+ {},
154
+ )
155
+ sess.commit()
156
+ args = g.user.id, report_run.id, contract_id, months, finish_date
157
+ threading.Thread(target=content, args=args).start()
158
+ return redirect(f"/report_runs/{report_run.id}", 303)
@@ -97,7 +97,18 @@
97
97
  <input type="submit" value="Download">
98
98
  </fieldset>
99
99
  </form>
100
- <br>
100
+
101
+ <form action="/reports/missing_bills">
102
+ <fieldset>
103
+ <legend>Download Missing Bills</legend>
104
+ <input type="hidden" name="contract_id" value="{{dc_contract.id}}">
105
+
106
+ <label>Months</label> <input name="months" value="1" maxlength="2" size="2">
107
+ <label>Last Month</label> {{input_date('finish', last_month_finish, 'month')}}
108
+ <input type="submit" value="Download">
109
+ </fieldset>
110
+ </form>
111
+
101
112
  <h3>Script</h3>
102
113
 
103
114
  <pre>{{dc_contract.charge_script}}</pre>
@@ -76,6 +76,17 @@
76
76
  <input type="submit" value="Download"/>
77
77
  </fieldset>
78
78
  </form>
79
+
80
+ <form action="/reports/missing_bills">
81
+ <fieldset>
82
+ <legend>Download Missing Bills</legend>
83
+ <input type="hidden" name="contract_id" value="{{contract.id}}">
84
+
85
+ <label>Months</label> <input name="months" value="1" maxlength="2" size="2">
86
+ <label>Last Month</label> {{input_date('finish', last_month_finish, 'month')}}
87
+ <input type="submit" value="Download">
88
+ </fieldset>
89
+ </form>
79
90
  <br>
80
91
  <h2>Script</h2>
81
92
  <pre>{{contract.charge_script}}</pre>
@@ -97,6 +97,17 @@
97
97
  </fieldset>
98
98
  </form>
99
99
 
100
+ <form action="/reports/missing_bills">
101
+ <fieldset>
102
+ <legend>Download Missing Bills</legend>
103
+ <input type="hidden" name="contract_id" value="{{contract.id}}">
104
+
105
+ <label>Months</label> <input name="months" value="1" maxlength="2" size="2">
106
+ <label>Last Month</label> {{input_date('finish', month_finish, 'month')}}
107
+ <input type="submit" value="Download">
108
+ </fieldset>
109
+ </form>
110
+
100
111
  <h2>Script</h2>
101
112
 
102
113
  <pre>{{contract.charge_script}}</pre>
@@ -0,0 +1,97 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Report Runs &raquo; {{run.id}}
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/report_runs">Report Runs</a> &raquo; {{run.id}}
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+ <table class="sticky">
13
+ <caption>Missing Electricity Bills Summary</caption>
14
+ <thead>
15
+ <tr>
16
+ <th>Date Created</th>
17
+ <th>Created By</th>
18
+ <th>State</th>
19
+ <th>Number Of Rows</th>
20
+ <th>Delete</th>
21
+ <th>Download Spreadsheet</th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <tr>
26
+ <td>{{run.date_created|hh_format}}</td>
27
+ <td>{{run.creator}}</td>
28
+ <td>{{run.state}}</td>
29
+ <td>{{rows|length}}</td>
30
+ <td>
31
+ <form hx-delete="/report_runs/{{run.id}}/edit"
32
+ hx-confirm="Are you sure you want to delete this report run?">
33
+ <fieldset style="border: none;">
34
+ <input type="submit" name="delete" value="Delete">
35
+ </fieldset>
36
+ </form>
37
+ </td>
38
+ <td>
39
+ <a href="/report_runs/{{run.id}}/spreadsheet">Download</a>
40
+ </td>
41
+ </tr>
42
+ </tbody>
43
+ </table>
44
+
45
+ <table class="sticky">
46
+ <caption>
47
+ Missing Electricity Bills
48
+ </caption>
49
+ <thead>
50
+ <tr>
51
+ <th>Contract</th>
52
+ <th>Month</th>
53
+ <th>Supply</th>
54
+ <th>Import MPAN</th>
55
+ <th>Export MPAN</th>
56
+ <th>Account</th>
57
+ <th>Site</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ {% for row in rows %}
62
+ {% set values = row.data['values'] %}
63
+ <tr>
64
+ <td>
65
+ {% if values['market_role_code'] == 'M' %}
66
+ <a href="/e/mop_contracts/{{values['contract_id']}}">{{values['contract_name']}}</a>
67
+ {% elif values['market_role_code'] == 'C' %}
68
+ <a href="/e/dc_contracts/{{values['contract_id']}}">{{values['contract_name']}}</a>
69
+ {% elif values['market_role_code'] == 'X' %}
70
+ <a href="/e/supplier_contracts/{{values['contract_id']}}">{{values['contract_name']}}</a>
71
+ {% endif %}
72
+ </td>
73
+ <td>{{values['month_start'][:7]}}</td>
74
+ <td>
75
+ <a href="/e/supplies/{{values['supply_id']}}">View</a>
76
+ </td>
77
+ <td>
78
+ {% if values['imp_mpan_core'] is not none %}
79
+ {{values['imp_mpan_core']}}
80
+ {% endif %}
81
+ </td>
82
+ <td>
83
+ {% if values['exp_mpan_core'] is not none %}
84
+ {{values['exp_mpan_core']}}
85
+ {% endif %}
86
+ </td>
87
+ <td>{{values['account']}}</td>
88
+ <td>
89
+ <a href="/sites/{{values['site_id']}}" title="{{values['site_name']}}">
90
+ {{values['site_code']}}
91
+ </a>
92
+ </td>
93
+ </tr>
94
+ {% endfor %}
95
+ </tbody>
96
+ </table>
97
+ {% endblock %}
chellow/utils.py CHANGED
@@ -67,12 +67,15 @@ def req_zish(name):
67
67
  def req_date(prefix, resolution="minute"):
68
68
  year = req_int(f"{prefix}_year")
69
69
  month = req_int(f"{prefix}_month")
70
- day = req_int(f"{prefix}_day")
71
70
 
72
71
  try:
73
- if resolution == "day":
72
+ if resolution == "month":
73
+ d = ct_datetime(year, month)
74
+ elif resolution == "day":
75
+ day = req_int(f"{prefix}_day")
74
76
  d = ct_datetime(year, month, day)
75
77
  elif resolution == "minute":
78
+ day = req_int(f"{prefix}_day")
76
79
  hour = req_int(f"{prefix}_hour")
77
80
  minute = req_int(f"{prefix}_minute")
78
81
  d = ct_datetime(year, month, day, hour, minute)
chellow/views.py CHANGED
@@ -1538,6 +1538,19 @@ def report_run_get(run_id):
1538
1538
  run=run,
1539
1539
  rows=rows,
1540
1540
  )
1541
+ elif run.name == "missing_e_bills":
1542
+ rows = g.sess.scalars(
1543
+ select(ReportRunRow)
1544
+ .filter(ReportRunRow.report_run == run)
1545
+ .order_by(
1546
+ ReportRunRow.data["values"]["month_start"],
1547
+ )
1548
+ ).all()
1549
+ return render_template(
1550
+ "report_run_missing_e_bills.html",
1551
+ run=run,
1552
+ rows=rows,
1553
+ )
1541
1554
 
1542
1555
  else:
1543
1556
  order_by = "row.id"
@@ -1561,12 +1574,12 @@ def report_run_get(run_id):
1561
1574
  )
1562
1575
 
1563
1576
 
1564
- @home.route("/report_runs/<int:run_id>", methods=["POST"])
1565
- def report_run_post(run_id):
1577
+ @home.route("/report_runs/<int:run_id>", methods=["DELETE"])
1578
+ def report_run_delete(run_id):
1566
1579
  run = g.sess.query(ReportRun).filter(ReportRun.id == run_id).one()
1567
1580
  run.delete(g.sess)
1568
1581
  g.sess.commit()
1569
- return redirect("/report_runs", 303)
1582
+ return hx_redirect("/report_runs", 303)
1570
1583
 
1571
1584
 
1572
1585
  @home.route("/report_runs/<int:run_id>/spreadsheet")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chellow
3
- Version: 1744720514.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)
@@ -12,8 +12,8 @@ chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
12
12
  chellow/rate_server.py,sha256=fg-Pf_9Hk3bXmC9riPQNGQxBvLvBa_WtNYdwDCjnCSg,5678
13
13
  chellow/rrun.py,sha256=1Kt2q_K9UoDG_nsZz-Q6XJiMNKroWqlqFdxn2M6Q8CA,2088
14
14
  chellow/testing.py,sha256=Dj2c1NX8lVlygueOrh2eyYawLW6qKEHxNhXVVUaNRO0,3637
15
- chellow/utils.py,sha256=Ej7dsbQ6Ee8X2aZ7B2Vs-hUFCsMABioAdOV1DJjwY-0,19293
16
- chellow/views.py,sha256=Ax8TW5dOoWN1G7zmmO4_X0af_9gftKU0HAHoPpkYNIc,84883
15
+ chellow/utils.py,sha256=i3GQK9MIcweosZk2gi-nX_IFq2DxURAJDyNoLBg6YwM,19421
16
+ chellow/views.py,sha256=GVmmChaMaFmZmcsgvR9Z5xfSh4Y1mPEBDR90kiZH5Xk,85292
17
17
  chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
19
19
  chellow/e/bill_importer.py,sha256=7UcnqNlKbJc2GhW9gy8sDp9GuqambJVpZLvbafOZztA,7411
@@ -22,7 +22,7 @@ chellow/e/bsuos.py,sha256=hdP9vnOJSuZl46OAkJeUg1XJYvYIBj4J6Sqce1Hy9Vs,15542
22
22
  chellow/e/ccl.py,sha256=30dh_SvlgzsTQPPAJNZWILaMvbeDsv9-P-S1JxS5_SQ,3184
23
23
  chellow/e/cfd.py,sha256=CWLdYeNjgqT6Ro8YRf4vhwXIAJ2aV4Wi6HLNClVSeaQ,14260
24
24
  chellow/e/computer.py,sha256=aGti5aXRTdC85M-WRiAoZkC_76sqfAiP-79ggWZF09Q,68020
25
- chellow/e/dno_rate_parser.py,sha256=A5TP6KjyfT5lVWh7dX4SiXRi6wnf2lGv-H_T4Sod8CI,21731
25
+ chellow/e/dno_rate_parser.py,sha256=NOVfS9HRDsc0rO282hU-IdrcuvMlC7VE8RySqd_5eT0,21762
26
26
  chellow/e/duos.py,sha256=RHrn93I1ASO2uYkuF18qlhG4p-jpuJhd_g3o69wtP4U,31004
27
27
  chellow/e/elexon.py,sha256=ALhXS9Es7PV0z9ukPbIramn3cf3iLyFi-PMWPSm5iOs,5487
28
28
  chellow/e/energy_management.py,sha256=aXC2qlGt3FAODlNl_frWzVYAQrJLP8FFOiNX3m-QE_Y,12388
@@ -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=MmGPNWYPY9UURvKwAHYpWQF2C8YuF8D1IVH9xptemDs,11227
63
- chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py,sha256=yogXTuQHGRL7IiqvRWr2C9V24ez1j9Yx0128UygPE_k,4723
62
+ chellow/e/bill_parsers/mm.py,sha256=tv7SAIbwFJYCiHz1ZwTV9QJeGkSo3-KuOp--DUimzLI,11227
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
@@ -107,6 +108,7 @@ chellow/reports/report_g_supplies_snapshot.py,sha256=9xB6RDrnbgxuomMcP1b1yEP4kOn
107
108
  chellow/reports/report_g_supply_virtual_bill.py,sha256=EaYrB8PHJIXrUuhiZ7dwUlbNBkuyJebQHrdc308_z1o,3653
108
109
  chellow/reports/report_g_virtual_bills.py,sha256=20vHa5LGQwOAlJlaGJaGszZrrbT0PMOZJf6hSxU2hIQ,4528
109
110
  chellow/reports/report_g_virtual_bills_hh.py,sha256=gaiLEmKTpq6JsfZ1p0SdCDuPvzvigXp6z88gHRCA63w,3416
111
+ chellow/reports/report_missing_bills.py,sha256=3uWA6Wyskn30tUDOV_W6u_009flfKzua7vDh_jsBDaM,5956
110
112
  chellow/reports/report_sscs.py,sha256=fQWyVG-gdg37DyNHgpNARpSxIwTl7mCn20fDLwx9oHg,3214
111
113
  chellow/reports/report_supply_contacts.py,sha256=pvwlInaPYV_pa9MMK6vh854plHFwv3m5zo5xulR1g5I,3599
112
114
  chellow/static/css/chellow.css,sha256=dnkuj9Z1BCOV_L2Y26lDd2QlTmFFhATa1YvwPVch1Oc,5375
@@ -145,6 +147,7 @@ chellow/templates/report_run_asset_comparison.html,sha256=VYCCUmIC7Mfe7uuaAHb6ih
145
147
  chellow/templates/report_run_bill_check.html,sha256=H2ayoBL7EgKMq2Nwq5VjE_TNvcIKcqeCm0alQWLIw78,5084
146
148
  chellow/templates/report_run_ecoes_comparison.html,sha256=VmkT5ypWLP8qZS6NbDTC4yDaG7mnUlxZz7EV8xkHGZw,4086
147
149
  chellow/templates/report_run_g_bill_check.html,sha256=tOXl_mjR__foYKiOYflJbK-459actAtjzv8rfuL3TwM,4851
150
+ chellow/templates/report_run_missing_e_bills.html,sha256=l5idQhfaNhMvvzIRv-iqCpeDnYl_wgs6-mZMBOmuyR8,2447
148
151
  chellow/templates/report_run_monthly_duration_org.html,sha256=gGNGJ4Q50q4BtIMi98rhO-7NqRHcsFUmbj2qzeOLejw,1713
149
152
  chellow/templates/report_run_row.html,sha256=bmtcdqJaS1CXpL0i8PuqvmeF98jKNYX5-mnQu-OuDKQ,3791
150
153
  chellow/templates/report_run_row_bill_check.html,sha256=aC2LMu_6NvmTN3ZdxHJPPPczyxPN6hg0F-PPcqIWUws,4683
@@ -200,7 +203,7 @@ chellow/templates/e/dc_bill_add.html,sha256=73Sn_MKBsUuYYnDfUMCdX1Dul6GimMC9YXk6
200
203
  chellow/templates/e/dc_bill_edit.html,sha256=0GsN-ZIc4q-z_xs8igC2ZS6t4soo2SvB3qRA6iC-AuM,2707
201
204
  chellow/templates/e/dc_bill_import.html,sha256=NHjMSoFizvFQIaPWuVE3nTCtMDTzJB0XmH8jXfV1tiA,2188
202
205
  chellow/templates/e/dc_bill_imports.html,sha256=lCaUR47r9KPr0VrQhEvVEaKexM5R_nmkxtzgpWZ0e9s,2135
203
- chellow/templates/e/dc_contract.html,sha256=b6DLDrGdzN9Rri56pFvfpE7QZKYXGHib2veYAztuHlg,2352
206
+ chellow/templates/e/dc_contract.html,sha256=MqkRck0qCgxXrHSbYTfyqjf_bAbIfMy93b9kdqH0pCQ,2746
204
207
  chellow/templates/e/dc_contract_edit.html,sha256=IObmbHQmeZ_LSpnYgabmhoSNUR3aPmm-Jk5nJLM_u74,1706
205
208
  chellow/templates/e/dc_contract_hh_import.html,sha256=JncR3L6cOK4jghsGyr-itEqlIemXBXt3kL09wpqnQSE,856
206
209
  chellow/templates/e/dc_contract_hh_imports.html,sha256=eXFDGyzSgag4JRism81_p5yTzQOjCIXaVkQ8tl3dDcM,8172
@@ -257,7 +260,7 @@ chellow/templates/e/mop_bill_add.html,sha256=fjoJj6QvcCk28rPD0Z6tIXrFYZ7ygN6lIXu
257
260
  chellow/templates/e/mop_bill_edit.html,sha256=Hld9b-ckLCd9N7hleugg0BuwsZrmkvDeiJ5yLW8z-Js,2748
258
261
  chellow/templates/e/mop_bill_import.html,sha256=sSVCZnarru0GKYFW1uuP0f1Ix9iNFDtEypQ93hqeUsk,3201
259
262
  chellow/templates/e/mop_bill_imports.html,sha256=jIhWaOuk4hYj9jhXBUxQ7C8v9HJljWfOGGeu1rZa1PI,1610
260
- chellow/templates/e/mop_contract.html,sha256=CyuBhj6da1-TiaomXSDD8JD449Ksu8QvIZODHmsE804,1748
263
+ chellow/templates/e/mop_contract.html,sha256=M6_pZuNpjdh9EaSGA5UsqSFIAvB0ABOehJ7s5oQ3pvk,2144
261
264
  chellow/templates/e/mop_contract_add.html,sha256=qyjDNz_pOVgfrsKaju0KwxBKJnWF9RxUa-8gUtaOc60,738
262
265
  chellow/templates/e/mop_contract_edit.html,sha256=4DbyiKbWRdliQGPh0wlh4dEuhD-R4-3XRzXoNMLfFD4,1719
263
266
  chellow/templates/e/mop_contracts.html,sha256=2TPVkeUefsYOVZIiiLMPJgwcrxhvjk8Io4UKvgdQ2AY,768
@@ -317,7 +320,7 @@ chellow/templates/e/supplier_bill_add.html,sha256=BsD-Zh7d9auiqJ61VPHiQrP8u8rTcw
317
320
  chellow/templates/e/supplier_bill_edit.html,sha256=oxZrMcMwrvluJSPxD4yfM9mWNeugoguAwT_ai9Ynl88,2732
318
321
  chellow/templates/e/supplier_bill_import.html,sha256=2_VvBoNE838UwuN0AiMwIrzqxQmWBo5DGY0lY833Bpk,5265
319
322
  chellow/templates/e/supplier_bill_imports.html,sha256=9iTNGWKn9XjQTBP1Sepbo0QVNlgKh7EfizXapam7I6s,9356
320
- chellow/templates/e/supplier_contract.html,sha256=oVGrYGIbCb-7MmpFQaXeTNRKBKy8GoGCWICWgo5E4xI,2863
323
+ chellow/templates/e/supplier_contract.html,sha256=TOm5ey5b1tm0A-8g2EmZhY7WddbGDih1B4ieJknA8Ns,3254
321
324
  chellow/templates/e/supplier_contract_add.html,sha256=gsozEtF24lzYi_Bb4LTenvh62tCt7dQ4CwaIz7rFck4,899
322
325
  chellow/templates/e/supplier_contract_edit.html,sha256=Afwtn0l8XRbt86bMOru2AjjvctoUkieD052aY0B2mDc,1463
323
326
  chellow/templates/e/supplier_contracts.html,sha256=VwWD4q88Fynz7vioFSAsyH6RR_1SyQQl6bQwzL-W1m0,1508
@@ -380,6 +383,6 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
380
383
  chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
381
384
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
382
385
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
383
- chellow-1744720514.0.0.dist-info/METADATA,sha256=h25ZnkwR-pmuFVXtxncX-IM8v7L7Z_yXQ4DZ_H-HlYw,12238
384
- chellow-1744720514.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
385
- chellow-1744720514.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,,